// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= Lightmass.h: lightmass import/export implementation. =============================================================================*/ #include "Lightmass/Lightmass.h" #include "HAL/FileManager.h" #include "Misc/Paths.h" #include "Misc/ScopeLock.h" #include "Misc/ConfigCacheIni.h" #include "Misc/FeedbackContext.h" #include "Misc/App.h" #include "UObject/UObjectIterator.h" #include "EngineDefines.h" #include "Engine/World.h" #include "StaticMeshLight.h" #include "PrecomputedLightVolume.h" #include "PrecomputedVolumetricLightmap.h" #include "Engine/MapBuildDataRegistry.h" #include "Engine/TextureLightProfile.h" #include "ModelLight.h" #include "LandscapeLight.h" #include "MaterialDomain.h" #include "Materials/Material.h" #include "Camera/CameraActor.h" #include "Components/PointLightComponent.h" #include "Components/SpotLightComponent.h" #include "Components/RectLightComponent.h" #include "Engine/GeneratedMeshAreaLight.h" #include "Components/DirectionalLightComponent.h" #include "Components/SkyLightComponent.h" #include "Rendering/SkyAtmosphereCommonData.h" #include "Components/ModelComponent.h" #include "Materials/MaterialInstanceConstant.h" #include "EngineUtils.h" #include "Editor.h" #include "LevelEditorViewport.h" #include "StaticMeshResources.h" #include "LightMap.h" #include "ShadowMap.h" #include "LandscapeProxy.h" #include "LandscapeComponent.h" #include "Components/SplineMeshComponent.h" #include "Lightmass/PrecomputedVisibilityVolume.h" #include "Lightmass/PrecomputedVisibilityOverrideVolume.h" #include "ComponentReregisterContext.h" #include "ShaderCompiler.h" #include "Engine/LevelStreaming.h" #include "UnrealEngine.h" #include "ComponentRecreateRenderStateContext.h" #include "EditorLevelUtils.h" #include "Misc/MessageDialog.h" #include "Modules/ModuleManager.h" #include "ImageCoreUtils.h" #include "Misc/FileHelper.h" #include "StaticLightingBuildContext.h" #include "Engine/InstancedStaticMesh.h" extern FSwarmDebugOptions GSwarmDebugOptions; //@todo_ow: Potentially move to core for sharing ? class FObjectMemoryWriter : public FMemoryWriter { public: FObjectMemoryWriter(TArray& Payload) : FMemoryWriter(Payload) { } virtual FArchive& operator<<(struct FSoftObjectPath& Value) { FString FullPath = Value.ToString(); *this << FullPath; return *this; } }; class FObjectMemoryReader : public FMemoryReader { public: FObjectMemoryReader(TArray& Payload) : FMemoryReader(Payload) { } virtual FArchive& operator<<(struct FSoftObjectPath& Value) { FString FullPath; *this << FullPath; Value = FSoftObjectPath(FullPath); return *this; } }; struct FDeferredMappingsBundle { // Map of all external GUIDs to internal mapping GUID in this bundle // This is used to map the owners actors to the mappings TMap OwnerToMapping; TMap MappingGuidToHelper; TArray Mappings; void Serialize(FArchive& Ar, bool bManifestOnly) { Ar << OwnerToMapping; if (!bManifestOnly) { Ar << Mappings; } } void MapHelpers() { MappingGuidToHelper.Reset(); OwnerToMapping.Reset(); for (const FLightmassProcessor::FTextureMappingImportHelper& Helper : Mappings) { MappingGuidToHelper.Add(Helper.MappingGuid, &Helper); OwnerToMapping.Add(Helper.OwnerGuid, Helper.MappingGuid); } } }; DEFINE_LOG_CATEGORY_STATIC(LogLightmassSolver, Warning, All); /** * If false (default behavior), Lightmass is launched automatically when a lighting build starts. * If true, it must be launched manually (e.g. through a debugger). */ UNREALED_API bool GLightmassDebugMode = false; /** If true, all participating Lightmass agents will report back detailed stats to the log. */ UNREALED_API bool GLightmassStatsMode = false; /*----------------------------------------------------------------------------- FSwarmDebugOptions -----------------------------------------------------------------------------*/ // Doxygen cannot parse this since FSwarmDebugOptions is declared in an engine header #if !UE_BUILD_DOCS void FSwarmDebugOptions::Touch() { //@todo For some reason, the global instance is not initializing to the default settings... if (bInitialized == false) { bDistributionEnabled = true; bForceContentExport = false; bInitialized = true; } } #endif #include "ImportExport.h" #include "MaterialExport.h" #include "StatsViewerModule.h" #include "LightingBuildInfo.h" #include "Logging/TokenizedMessage.h" #include "Logging/MessageLog.h" #include "Misc/UObjectToken.h" #define LOCTEXT_NAMESPACE "Lightmass" /** The number of available mappings to process before yielding back to the importing. */ int32 FLightmassProcessor::MaxProcessAvailableCount = 8; /** We don't want any amortization steps to take longer than this amount every tick */ static const float AllowedAmortizationTimePerTick = 0.01f; // in seconds volatile int32 FLightmassProcessor::VolumeSampleTaskCompleted = 0; volatile int32 FLightmassProcessor::MeshAreaLightDataTaskCompleted = 0; volatile int32 FLightmassProcessor::VolumeDistanceFieldTaskCompleted = 0; /** Flags to use when opening the different kinds of input channels */ /** MUST PAIR APPROPRIATELY WITH THE SAME FLAGS IN LIGHTMASS */ static NSwarm::TChannelFlags LM_TEXTUREMAPPING_CHANNEL_FLAGS = NSwarm::SWARM_JOB_CHANNEL_READ; static NSwarm::TChannelFlags LM_VERTEXMAPPING_CHANNEL_FLAGS = NSwarm::SWARM_JOB_CHANNEL_READ; static NSwarm::TChannelFlags LM_VOLUMESAMPLES_CHANNEL_FLAGS = NSwarm::SWARM_JOB_CHANNEL_READ; static NSwarm::TChannelFlags LM_PRECOMPUTEDVISIBILITY_CHANNEL_FLAGS = NSwarm::SWARM_JOB_CHANNEL_READ; static NSwarm::TChannelFlags LM_VOLUMEDEBUGOUTPUT_CHANNEL_FLAGS = NSwarm::SWARM_JOB_CHANNEL_READ; static NSwarm::TChannelFlags LM_DOMINANTSHADOW_CHANNEL_FLAGS = NSwarm::SWARM_JOB_CHANNEL_READ; static NSwarm::TChannelFlags LM_MESHAREALIGHT_CHANNEL_FLAGS = NSwarm::SWARM_JOB_CHANNEL_READ; static NSwarm::TChannelFlags LM_DEBUGOUTPUT_CHANNEL_FLAGS = NSwarm::SWARM_JOB_CHANNEL_READ; /** Flags to use when opening the different kinds of output channels */ /** MUST PAIR APPROPRIATELY WITH THE SAME FLAGS IN LIGHTMASS */ #if LM_COMPRESS_INPUT_DATA static NSwarm::TChannelFlags LM_SCENE_CHANNEL_FLAGS = (NSwarm::TChannelFlags)(NSwarm::SWARM_JOB_CHANNEL_WRITE | NSwarm::SWARM_CHANNEL_MISC_ENABLE_COMPRESSION); static NSwarm::TChannelFlags LM_STATICMESH_CHANNEL_FLAGS = (NSwarm::TChannelFlags)(NSwarm::SWARM_CHANNEL_WRITE | NSwarm::SWARM_CHANNEL_MISC_ENABLE_COMPRESSION); static NSwarm::TChannelFlags LM_TERRAIN_CHANNEL_FLAGS = (NSwarm::TChannelFlags)(NSwarm::SWARM_CHANNEL_WRITE | NSwarm::SWARM_CHANNEL_MISC_ENABLE_COMPRESSION); static NSwarm::TChannelFlags LM_MATERIAL_CHANNEL_FLAGS = (NSwarm::TChannelFlags)(NSwarm::SWARM_CHANNEL_WRITE | NSwarm::SWARM_CHANNEL_MISC_ENABLE_COMPRESSION); #else static NSwarm::TChannelFlags LM_SCENE_CHANNEL_FLAGS = NSwarm::SWARM_JOB_CHANNEL_WRITE; static NSwarm::TChannelFlags LM_STATICMESH_CHANNEL_FLAGS = NSwarm::SWARM_CHANNEL_WRITE; static NSwarm::TChannelFlags LM_TERRAIN_CHANNEL_FLAGS = NSwarm::SWARM_CHANNEL_WRITE; static NSwarm::TChannelFlags LM_MATERIAL_CHANNEL_FLAGS = NSwarm::SWARM_CHANNEL_WRITE; #endif #define VERIFYLIGHTMASSINI(x) {bool bSucceeded = x; if (!bSucceeded) { VerifyLightmassIni(#x,__FILE__,__LINE__); }} void VerifyLightmassIni(const ANSICHAR* Code,const ANSICHAR* Filename,uint32 Line) { if (FApp::IsUnattended()) { UE_LOG(LogLightmassSolver, Fatal,TEXT("%s failed \n at %s:%u"),ANSI_TO_TCHAR(Code),ANSI_TO_TCHAR(Filename),Line); } else { FString Error = FString::Printf( TEXT("Fatal error: A required key was missing from BaseLightmass.ini. This can happen if BaseLightmass.ini is overwritten with an old version.\n") TEXT("Create a DefaultLightmass.ini in your project and override just the values you need, then the overrides will continue to work on version upgrades.\n") TEXT("https://docs.unrealengine.com/latest/INT/Programming/Basics/ConfigurationFiles\n\n") TEXT("%s failed \n at %s:%u"), ANSI_TO_TCHAR(Code),ANSI_TO_TCHAR(Filename),Line); FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(Error)); FPlatformMisc::RequestExit(1); } } /*----------------------------------------------------------------------------- FLightmassExporter -----------------------------------------------------------------------------*/ void Copy( const ULightComponentBase* In, Lightmass::FLightData& Out ) { FMemory::Memzero(Out); Out.LightFlags = 0; if (In->CastShadows) { Out.LightFlags |= Lightmass::GI_LIGHT_CASTSHADOWS; } if (In->HasStaticLighting()) { Out.LightFlags |= Lightmass::GI_LIGHT_HASSTATICSHADOWING; Out.LightFlags |= Lightmass::GI_LIGHT_HASSTATICLIGHTING; } else if (In->HasStaticShadowing()) { Out.LightFlags |= Lightmass::GI_LIGHT_STORE_SEPARATE_SHADOW_FACTOR; Out.LightFlags |= Lightmass::GI_LIGHT_HASSTATICSHADOWING; } if (In->CastStaticShadows) { Out.LightFlags |= Lightmass::GI_LIGHT_CASTSTATICSHADOWS; } Out.Color = In->LightColor; // Set brightness here for light types that only derive from ULightComponentBase and not from ULightComponent Out.Brightness = In->Intensity; Out.Guid = In->LightGuid; Out.IndirectLightingScale = In->IndirectLightingIntensity; } void Copy( const ULightComponent* In, Lightmass::FLightData& Out ) { Copy((const ULightComponentBase*)In, Out); const ULocalLightComponent* LocalLight = Cast(In); const UPointLightComponent* PointLight = Cast(In); if( ( LocalLight && LocalLight->GetLightType() == LightType_Rect ) || ( PointLight && PointLight->bUseInverseSquaredFalloff ) ) { Out.LightFlags |= Lightmass::GI_LIGHT_INVERSE_SQUARED; } if (In->GetLightmassSettings().bUseAreaShadowsForStationaryLight) { Out.LightFlags |= Lightmass::GI_LIGHT_USE_AREA_SHADOWS_FOR_SEPARATE_SHADOW_FACTOR; } Out.Brightness = In->ComputeLightBrightness(); Out.Position = (FVector4f)In->GetLightPosition(); Out.Direction = (FVector3f)In->GetDirection(); if( In->bUseTemperature ) { Out.Color *= In->GetColorTemperature(); } } void CopyLightProfile( const ULightComponentBase* In, Lightmass::FLightData& Out, TArray< uint8 >& OutLightProfileTextureData ) { OutLightProfileTextureData.Empty( Lightmass::FLightData::LightProfileTextureDataSize ); OutLightProfileTextureData.AddUninitialized( Lightmass::FLightData::LightProfileTextureDataSize ); FMemory::Memset(OutLightProfileTextureData.GetData(), 0xff, OutLightProfileTextureData.Num() * OutLightProfileTextureData.GetTypeSize()); } void CopyLightProfile( const ULightComponent* In, Lightmass::FLightData& Out, TArray< uint8 >& OutLightProfileTextureData ) { CopyLightProfile( (const ULightComponentBase*)In, Out, OutLightProfileTextureData ); if(In->IESTexture) { FTextureSource& Source = In->IESTexture->Source; // The current IES importer only uses this input format // even if we change the actual texture format this shouldn't change if( Source.GetFormat() == TSF_RGBA16F && Source.GetSizeX() * Source.GetSizeY() <= Lightmass::FLightData::LightProfileTextureDataSize ) { Out.LightFlags |= Lightmass::GI_LIGHT_USE_LIGHTPROFILE; TArray64 MipData; verify( Source.GetMipData(MipData, 0) ); const uint32 Width = FMath::Sqrt( static_cast(Lightmass::FLightData::LightProfileTextureDataSize) ); const uint32 Height = Lightmass::FLightData::LightProfileTextureDataSize / Width; for(uint32 y = 0; y < Height; ++y) { uint32 SourceY = FMath::Min( y, (uint32)Source.GetSizeY() - 1 ); // We'll repeat the data if the source is smaller than the destination (we used to have 1D textures for IES files) for(uint32 x = 0; x < Width; ++x) { uint32 SourceX = FMath::Min( x, (uint32)Source.GetSizeX() - 1 ); FFloat16 HalfValue = *(FFloat16*)&MipData[ (SourceY * (uint32)Source.GetSizeX() * 8) + (SourceX * 8) ]; float Value = HalfValue; OutLightProfileTextureData[y * Width + x] = (uint8)(Value * 255.f + 0.5f); } } } } } FORCEINLINE void Copy( const FSplineMeshParams& In, Lightmass::FSplineMeshParams& Out ) { Out.StartPos = FVector3f(In.StartPos); //LWC_TODO: Precision loss Out.StartTangent = FVector3f(In.StartTangent); Out.StartScale = FVector2f(In.StartScale); // LWC_TODO: Precision loss Out.StartRoll = In.StartRoll; Out.StartOffset = FVector2f(In.StartOffset); // LWC_TODO: Precision loss Out.EndPos = FVector3f(In.EndPos); //LWC_TODO: Precision loss Out.EndTangent = FVector3f(In.EndTangent); Out.EndScale = FVector2f(In.EndScale); // LWC_TODO: Precision loss Out.EndOffset = FVector2f(In.EndOffset); // LWC_TODO: Precision loss Out.EndRoll = In.EndRoll; } void FLightmassProcessor::ProcessAlertMessages() { FScopeLock Lock(&SwarmCallbackMessagesSection); for (int32 MessageIndex = 0; MessageIndex < SwarmCallbackMessages.Num(); MessageIndex++) { FLightmassAlertMessage& AlertMessage = SwarmCallbackMessages[MessageIndex]; UObject* Object = NULL; switch (AlertMessage.Type) { case Lightmass::SOURCEOBJECTTYPE_StaticMesh: Object = FindStaticMesh(AlertMessage.ObjectId); break; case Lightmass::SOURCEOBJECTTYPE_Mapping: const FStaticLightingMapping* FoundMapping = GetLightmassExporter()->FindMappingByGuid(AlertMessage.ObjectId); if (FoundMapping) { Object = FoundMapping->GetMappedObject(); } break; } FText LocalizedMessage; const FText* LocalizedMessagePtr = Messages.Find( FString( AlertMessage.MessageText ) ); if ( LocalizedMessagePtr == NULL ) { LocalizedMessage = FText::FromString( FString( AlertMessage.MessageText ) ); LocalizedMessagePtr = &LocalizedMessage; } FMessageLog("LightingResults").Message((EMessageSeverity::Type)AlertMessage.Severity) ->AddToken(FUObjectToken::Create(Object)) ->AddToken(FTextToken::Create( *LocalizedMessagePtr )); } SwarmCallbackMessages.Empty(); } /*----------------------------------------------------------------------------- SwarmCallback function -----------------------------------------------------------------------------*/ void FLightmassProcessor::SwarmCallback( NSwarm::FMessage* CallbackMessage, void* CallbackData ) { FLightmassProcessor* Processor = (FLightmassProcessor*) CallbackData; double SwarmCallbackStartTime = FPlatformTime::Seconds(); switch ( CallbackMessage->Type ) { case NSwarm::MESSAGE_JOB_STATE: { NSwarm::FJobState* JobStateMessage = (NSwarm::FJobState*)CallbackMessage; switch (JobStateMessage->JobState) { case NSwarm::JOB_STATE_INVALID: Processor->bProcessingFailed = true; break; case NSwarm::JOB_STATE_RUNNING: break; case NSwarm::JOB_STATE_COMPLETE_SUCCESS: Processor->bProcessingSuccessful = true; break; case NSwarm::JOB_STATE_COMPLETE_FAILURE: Processor->bProcessingFailed = true; break; case NSwarm::JOB_STATE_KILLED: Processor->bProcessingFailed = true; break; default: break; } } break; case NSwarm::MESSAGE_TASK_STATE: { NSwarm::FTaskState* TaskStateMessage = (NSwarm::FTaskState*)CallbackMessage; switch (TaskStateMessage->TaskState) { case NSwarm::JOB_TASK_STATE_INVALID: // Consider this cause for failing the entire Job Processor->bProcessingFailed = true; break; case NSwarm::JOB_TASK_STATE_ACCEPTED: break; case NSwarm::JOB_TASK_STATE_REJECTED: // Consider this cause for failing the entire Job Processor->bProcessingFailed = true; break; case NSwarm::JOB_TASK_STATE_RUNNING: break; case NSwarm::JOB_TASK_STATE_COMPLETE_SUCCESS: { FGuid PrecomputedVolumeLightingGuid = Lightmass::PrecomputedVolumeLightingGuid; FGuid MeshAreaLightDataGuid = Lightmass::MeshAreaLightDataGuid; FGuid VolumeDistanceFieldGuid = Lightmass::VolumeDistanceFieldGuid; if (TaskStateMessage->TaskGuid == PrecomputedVolumeLightingGuid) { FPlatformAtomics::InterlockedIncrement( &VolumeSampleTaskCompleted ); FPlatformAtomics::InterlockedIncrement( &Processor->NumCompletedTasks ); } else if (Processor->Exporter->VisibilityBucketGuids.Contains(TaskStateMessage->TaskGuid)) { TList* NewElement = new TList(TaskStateMessage->TaskGuid, NULL); Processor->CompletedVisibilityTasks.AddElement( NewElement ); FPlatformAtomics::InterlockedIncrement( &Processor->NumCompletedTasks ); } else if (Processor->Exporter->VolumetricLightmapTaskGuids.Contains(TaskStateMessage->TaskGuid)) { TList* NewElement = new TList(TaskStateMessage->TaskGuid, NULL); Processor->CompletedVolumetricLightmapTasks.AddElement( NewElement ); FPlatformAtomics::InterlockedIncrement( &Processor->NumCompletedTasks ); FPlatformAtomics::InterlockedIncrement( &Processor->NumCompletedVolumetricLightmapTasks ); } else if (TaskStateMessage->TaskGuid == MeshAreaLightDataGuid) { FPlatformAtomics::InterlockedIncrement( &MeshAreaLightDataTaskCompleted ); FPlatformAtomics::InterlockedIncrement( &Processor->NumCompletedTasks ); } else if (TaskStateMessage->TaskGuid == VolumeDistanceFieldGuid) { FPlatformAtomics::InterlockedIncrement( &VolumeDistanceFieldTaskCompleted ); FPlatformAtomics::InterlockedIncrement( &Processor->NumCompletedTasks ); } else { // Add a mapping to the list of mapping GUIDs that have been completed TList* NewElement = new TList(TaskStateMessage->TaskGuid, NULL); Processor->CompletedMappingTasks.AddElement( NewElement ); FPlatformAtomics::InterlockedIncrement( &Processor->NumCompletedTasks ); } break; } case NSwarm::JOB_TASK_STATE_COMPLETE_FAILURE: { // Add a mapping to the list of mapping GUIDs that have been completed TList* NewElement = new TList(TaskStateMessage->TaskGuid, NULL); Processor->CompletedMappingTasks.AddElement( NewElement ); FPlatformAtomics::InterlockedIncrement( &Processor->NumCompletedTasks ); // Consider this cause for failing the entire Job Processor->bProcessingFailed = true; break; } case NSwarm::JOB_TASK_STATE_KILLED: break; default: break; } } break; case NSwarm::MESSAGE_INFO: { #if !NO_LOGGING && !(UE_BUILD_SHIPPING || UE_BUILD_TEST) NSwarm::FInfoMessage* InfoMessage = (NSwarm::FInfoMessage*)CallbackMessage; GLog->Log( InfoMessage->TextMessage ); #endif } break; case NSwarm::MESSAGE_ALERT: { NSwarm::FAlertMessage* AlertMessage = (NSwarm::FAlertMessage*)CallbackMessage; EMessageSeverity::Type CheckType = EMessageSeverity::Info; switch (AlertMessage->AlertLevel) { case NSwarm::ALERT_LEVEL_INFO: break; case NSwarm::ALERT_LEVEL_WARNING: CheckType = EMessageSeverity::Warning; break; case NSwarm::ALERT_LEVEL_ERROR: CheckType = EMessageSeverity::Error; break; } FGuid ObjectGuid = FGuid( AlertMessage->ObjectGuid.A, AlertMessage->ObjectGuid.B, AlertMessage->ObjectGuid.C, AlertMessage->ObjectGuid.D); { // Enqueue the message for the main thread to process, because FMessageLog isn't thread safe FScopeLock Lock(&Processor->SwarmCallbackMessagesSection); FLightmassAlertMessage NewMessage; NewMessage.ObjectId = ObjectGuid; NewMessage.MessageText = AlertMessage->TextMessage; NewMessage.Type = AlertMessage->TypeId; NewMessage.Severity = CheckType; Processor->SwarmCallbackMessages.Push(NewMessage); } } break; case NSwarm::MESSAGE_QUIT: { Processor->bQuitReceived = true; } break; } Processor->Statistics.SwarmCallbackTime += FPlatformTime::Seconds() - SwarmCallbackStartTime; } /*----------------------------------------------------------------------------- FLightmassExporter -----------------------------------------------------------------------------*/ FLightmassExporter::FLightmassExporter(const FStaticLightingBuildContext& Context) : Swarm( NSwarm::FSwarmInterface::Get() ) , SkyAtmosphereComponent(nullptr) , ExportStage(NotRunning) , CurrentAmortizationIndex(0) , OpenedMaterialExportChannels() , World( Context.World ) , LightingContext(Context) { // We must have a valid world check( World ); if (GLightmassDebugOptions.bDebugMode) { SceneGuid = FGuid(0x0123, 0x4567, 0x89AB, 0xCDEF); } else { SceneGuid = FGuid::NewGuid(); } // Compute the hash of UnrealLightmass executable and store it into LightmassExecutableHash // Guaranteed success as the executable has been checked by CheckLightmassExecutableVersion() TArray Bytes; FFileHelper::LoadFileToArray(Bytes, *FPlatformProcess::GenerateApplicationPath(TEXT("UnrealLightmass"), EBuildConfiguration::Development)); FSHA1::HashBuffer(&Bytes[0], Bytes.Num(), LightmassExecutableHash.Hash); ChannelName = Lightmass::CreateChannelName(SceneGuid, Lightmass::LM_SCENE_VERSION, Lightmass::LM_SCENE_EXTENSION); } FLightmassExporter::~FLightmassExporter() { // clean up any opened channels that are opened during export if (ExportStage == ExportMaterials) { for (int32 i = 0; i < OpenedMaterialExportChannels.Num(); ++i) { int32 Result = Swarm.CloseChannel(OpenedMaterialExportChannels[i]); } } else if (ExportStage == CleanupMaterialExport) { for (int32 i = CurrentAmortizationIndex; i < OpenedMaterialExportChannels.Num(); ++i) { int32 Result = Swarm.CloseChannel(OpenedMaterialExportChannels[i]); } } } void FLightmassExporter::AddMaterial(UMaterialInterface* InMaterialInterface, const FStaticLightingMesh* InStaticLightingMesh /*= nullptr*/) { if (InMaterialInterface) { FLightmassMaterialExportSettings ExportSettings = { InStaticLightingMesh }; if (auto* ExistingExportSettings = MaterialExportSettings.Find(InMaterialInterface)) { ensureMsgf(ExportSettings == *ExistingExportSettings, TEXT("Attempting to add the same material twice with different export settings, this is not (currently) supported")); return; } // Check for material texture changes... //@TODO: Add package to warning list if it needs to be resaved (perf warning) InMaterialInterface->UpdateLightmassTextureTracking(); Materials.Add(InMaterialInterface); MaterialExportSettings.Add(InMaterialInterface, ExportSettings); } } const FStaticLightingMapping* FLightmassExporter::FindMappingByGuid(FGuid FindGuid) const { for( int32 MappingIdx=0; MappingIdx < BSPSurfaceMappings.Num(); MappingIdx++ ) { const FStaticLightingMapping* CurrentMapping = BSPSurfaceMappings[MappingIdx]; if (CurrentMapping->GetLightingGuid() == FindGuid) { return CurrentMapping; } } for( int32 MappingIdx=0; MappingIdx < StaticMeshTextureMappings.Num(); MappingIdx++ ) { const FStaticLightingMapping* CurrentMapping = StaticMeshTextureMappings[MappingIdx]; if (CurrentMapping->GetLightingGuid() == FindGuid) { return CurrentMapping; } } for( int32 MappingIdx=0; MappingIdx < LandscapeTextureMappings.Num(); MappingIdx++ ) { const FStaticLightingMapping* CurrentMapping = LandscapeTextureMappings[MappingIdx]; if (CurrentMapping->GetLightingGuid() == FindGuid) { return CurrentMapping; } } for( int32 MappingIdx=0; MappingIdx < VolumeMappings.Num(); MappingIdx++ ) { const FStaticLightingMapping* CurrentMapping = VolumeMappings[MappingIdx]; if (CurrentMapping->GetLightingGuid() == FindGuid) { return CurrentMapping; } } for( int32 MappingIdx=0; MappingIdx < LandscapeVolumeMappings.Num(); MappingIdx++ ) { const FStaticLightingMapping* CurrentMapping = LandscapeVolumeMappings[MappingIdx]; if (CurrentMapping->GetLightingGuid() == FindGuid) { return CurrentMapping; } } return NULL; } void FLightmassExporter::WriteToChannel( FLightmassStatistics& Stats, FGuid& DebugMappingGuid ) { // Initialize the debug mapping Guid to something not in the scene. DebugMappingGuid = FGuid(0x96dc6516, 0xa616421d, 0x82f0ef5b, 0x299152b5); if( bSwarmConnectionIsValid ) { int32 Channel = Swarm.OpenChannel( *ChannelName, LM_SCENE_CHANNEL_FLAGS ); if( Channel >= 0 ) { // Ensure the default material is present... AddMaterial(UMaterial::GetDefaultMaterial(MD_Surface)); TotalProgress = DirectionalLights.Num() + PointLights.Num() + SpotLights.Num() + RectLights.Num() + SkyLights.Num() + StaticMeshes.Num() + StaticMeshLightingMeshes.Num() + StaticMeshTextureMappings.Num() + BSPSurfaceMappings.Num() + VolumeMappings.Num() + Materials.Num() + + LandscapeLightingMeshes.Num() + LandscapeTextureMappings.Num() + LandscapeVolumeMappings.Num(); CurrentProgress = 0; // Export scene header. Lightmass::FSceneFileHeader Scene; Scene.Cookie = 'SCEN'; Scene.FormatVersion = FGuid( 0, 0, 0, 1 ); Scene.Guid = FGuid( 0, 0, 0, 1 ); WriteSceneSettings(Scene); WriteDebugInput(Scene.DebugInput, DebugMappingGuid); /** If true, pad the mappings (shrink the requested size and then pad) */ Scene.bPadMappings = GLightmassDebugOptions.bPadMappings; Scene.bDebugPadding = GLightmassDebugOptions.bDebugPaddings; Scene.ExecutionTimeDivisor = GLightmassDebugOptions.ExecutionTimeDivisor; Scene.bColorByExecutionTime = GLightmassDebugOptions.bColorByExecutionTime; Scene.bUseRandomColors = GLightmassDebugOptions.bUseRandomColors; Scene.bColorBordersGreen = GLightmassDebugOptions.bColorBordersGreen; Scene.bOnlyCalcDebugTexelMappings = GLightmassDebugOptions.bOnlyCalcDebugTexelMappings; Scene.NumImportanceVolumes = ImportanceVolumes.Num(); Scene.NumCharacterIndirectDetailVolumes = CharacterIndirectDetailVolumes.Num(); Scene.NumVolumetricLightmapDensityVolumes = VolumetricLightmapDensityVolumes.Num(); Scene.NumPortals = Portals.Num(); Scene.NumDirectionalLights = DirectionalLights.Num(); Scene.NumPointLights = PointLights.Num(); Scene.NumSpotLights = SpotLights.Num(); Scene.NumRectLights = RectLights.Num(); Scene.NumSkyLights = SkyLights.Num(); Scene.NumStaticMeshes = StaticMeshes.Num(); Scene.NumStaticMeshInstances = StaticMeshLightingMeshes.Num(); Scene.NumFluidSurfaceInstances = 0; Scene.NumLandscapeInstances = LandscapeLightingMeshes.Num(); Scene.NumBSPMappings = BSPSurfaceMappings.Num(); Scene.NumStaticMeshTextureMappings = StaticMeshTextureMappings.Num(); Scene.NumFluidSurfaceTextureMappings = 0; Scene.NumLandscapeTextureMappings = LandscapeTextureMappings.Num(); Scene.NumSpeedTreeMappings = 0; Scene.NumVolumeMappings = VolumeMappings.Num(); Scene.NumLandscapeVolumeMappings = LandscapeVolumeMappings.Num(); Scene.NumPrecomputedVisibilityBuckets = VisibilityBucketGuids.Num(); Scene.NumVolumetricLightmapTasks = VolumetricLightmapTaskGuids.Num(); Swarm.WriteChannel( Channel, &Scene, sizeof(Scene) ); const int32 UserNameLength = FCString::Strlen(FPlatformProcess::UserName()); Swarm.WriteChannel( Channel, &UserNameLength, sizeof(UserNameLength) ); Swarm.WriteChannel( Channel, FPlatformProcess::UserName(), UserNameLength * sizeof(TCHAR) ); const int32 LevelNameLength = LevelName.Len(); Swarm.WriteChannel( Channel, &LevelNameLength, sizeof(LevelNameLength) ); Swarm.WriteChannel( Channel, *LevelName, LevelName.Len() * sizeof(TCHAR) ); for (int32 VolumeIndex = 0; VolumeIndex < ImportanceVolumes.Num(); VolumeIndex++) { FBox3f LMBox(ImportanceVolumes[VolumeIndex]); Swarm.WriteChannel(Channel, &LMBox, sizeof(LMBox)); } for (int32 VolumeIndex = 0; VolumeIndex < CharacterIndirectDetailVolumes.Num(); VolumeIndex++) { FBox3f LMBox(CharacterIndirectDetailVolumes[VolumeIndex]); Swarm.WriteChannel(Channel, &LMBox, sizeof(LMBox)); } for (int32 PortalIndex = 0; PortalIndex < Portals.Num(); PortalIndex++) { FMatrix44f Matrix(Portals[PortalIndex]); Swarm.WriteChannel(Channel, &Matrix, sizeof(Matrix)); } { FLightmassStatistics::FScopedGather VisibilityStat(Stats.ExportVisibilityDataTime); WriteVisibilityData(Channel); } { FLightmassStatistics::FScopedGather VisibilityStat(Stats.ExportVolumetricLightmapDataTime); WriteVolumetricLightmapData(Channel); } { FLightmassStatistics::FScopedGather LightStat(Stats.ExportLightsTime); WriteLights( Channel ); } { FLightmassStatistics::FScopedGather ModelStat(Stats.ExportModelsTime); WriteModels(); } { FLightmassStatistics::FScopedGather StaticMeshStat(Stats.ExportStaticMeshesTime); WriteStaticMeshes(); } { FLightmassStatistics::FScopedGather MeshInstanceStat(Stats.ExportMeshInstancesTime); WriteMeshInstances( Channel ); } { FLightmassStatistics::FScopedGather LandscapeInstanceStat(Stats.ExportLandscapeInstancesTime); WriteLandscapeInstances( Channel ); } { FLightmassStatistics::FScopedGather MappingStat(Stats.ExportMappingsTime); WriteMappings( Channel ); } Swarm.CloseChannel( Channel ); } else { UE_LOG(LogLightmassSolver, Log, TEXT("Error, OpenChannel failed to open %s with error code %d"), *ChannelName, Channel ); } } } bool FLightmassExporter::WriteToMaterialChannel(FLightmassStatistics& Stats) { if (bSwarmConnectionIsValid && !GEditor->GetMapBuildCancelled()) { if (ExportStage == NotRunning) {ExportStage = BuildMaterials;} double ExportTime = 0.0; while (ExportTime < AllowedAmortizationTimePerTick && ExportStage != Complete) { FLightmassStatistics::FScopedGather ExportStat(ExportTime); switch (ExportStage) { case BuildMaterials: { if (CurrentAmortizationIndex >= Materials.Num()) { ExportStage = ShaderCompilation; CurrentAmortizationIndex = 0; } else { BuildMaterialMap(Materials[CurrentAmortizationIndex]); ++CurrentAmortizationIndex; } } break; case ShaderCompilation: { BlockOnShaderCompilation(); ExportStage = ExportMaterials; CurrentAmortizationIndex = 0; } break; case ExportMaterials: { if (CurrentAmortizationIndex >= Materials.Num()) { ExportStage = CleanupMaterialExport; CurrentAmortizationIndex = 0; } else { auto* CurrentMaterial = Materials[CurrentAmortizationIndex]; ExportMaterial(CurrentMaterial, MaterialExportSettings.FindChecked(CurrentMaterial)); ++CurrentAmortizationIndex; } } break; case CleanupMaterialExport: { if (CurrentAmortizationIndex >= OpenedMaterialExportChannels.Num()) { ExportStage = Complete; CurrentAmortizationIndex = 0; } else { Swarm.CloseChannel(OpenedMaterialExportChannels[CurrentAmortizationIndex]); ++CurrentAmortizationIndex; } } break; default: UE_LOG(LogLightmassSolver, Fatal, TEXT("Invalid amortization stage hit.")); } } Stats.ExportMaterialsTime += ExportTime; return ExportStage == Complete; } else { return true; } } float FLightmassExporter::GetAmortizedExportPercentDone() const { int32 EstimatedTotalTaskCount = Materials.Num() * 3; int32 CurrentTaskId = ExportStage == BuildMaterials ? CurrentAmortizationIndex: ExportStage == ShaderCompilation ? Materials.Num(): ExportStage == ExportMaterials ? (Materials.Num() + CurrentAmortizationIndex) : ExportStage == CleanupMaterialExport ? (Materials.Num() * 2 + CurrentAmortizationIndex) : EstimatedTotalTaskCount; return static_cast(CurrentTaskId) / EstimatedTotalTaskCount; } void FLightmassExporter::WriteVisibilityData( int32 Channel ) { Swarm.WriteChannel( Channel, VisibilityBucketGuids.GetData(), VisibilityBucketGuids.Num() * VisibilityBucketGuids.GetTypeSize() ); int32 NumVisVolumes = 0; for( TObjectIterator It; It; ++It ) { if (World->ContainsActor(*It) && IsValid(*It)) { NumVisVolumes++; } } if (World->GetWorldSettings()->bPrecomputeVisibility && NumVisVolumes == 0 && World->GetWorldSettings()->bPlaceCellsOnlyAlongCameraTracks == false) { FMessageLog("LightingResults").Error(LOCTEXT("LightmassError_MissingPrecomputedVisibilityVolume", "Level has bPrecomputeVisibility enabled but no Precomputed Visibility Volumes, precomputed visibility will not be effective.")); } // Export the visibility volumes that indicate to lightmass where to place visibility cells Swarm.WriteChannel( Channel, &NumVisVolumes, sizeof(NumVisVolumes) ); for( TObjectIterator It; It; ++It ) { APrecomputedVisibilityVolume* Volume = *It; if (World->ContainsActor(Volume) && IsValid(Volume)) { FBox3f LMBox(Volume->GetComponentsBoundingBox(true)); Swarm.WriteChannel(Channel, &LMBox, sizeof(LMBox)); TArray Planes4d; Volume->Brush->GetSurfacePlanes(Volume, Planes4d); TArray Planes; for (FPlane Plane4d : Planes4d) { Planes.Add(FPlane4f(Plane4d)); } const int32 NumPlanes = Planes.Num(); Swarm.WriteChannel( Channel, &NumPlanes, sizeof(NumPlanes) ); Swarm.WriteChannel( Channel, Planes.GetData(), Planes.Num() * Planes.GetTypeSize() ); } } int32 NumOverrideVisVolumes = 0; for( TObjectIterator It; It; ++It ) { if (World->ContainsActor(*It) && IsValid(*It)) { NumOverrideVisVolumes++; } } Swarm.WriteChannel( Channel, &NumOverrideVisVolumes, sizeof(NumOverrideVisVolumes) ); for( TObjectIterator It; It; ++It ) { APrecomputedVisibilityOverrideVolume* Volume = *It; if (World->ContainsActor(Volume) && IsValid(Volume)) { FBox3f LMBox(Volume->GetComponentsBoundingBox(true)); Swarm.WriteChannel(Channel, &LMBox, sizeof(LMBox)); TArray VisibilityIds; for (int32 ActorIndex = 0; ActorIndex < Volume->OverrideVisibleActors.Num(); ActorIndex++) { AActor* CurrentActor = Volume->OverrideVisibleActors[ActorIndex]; if (CurrentActor) { for (UActorComponent* Component : CurrentActor->GetComponents()) { UPrimitiveComponent* CurrentComponent = Cast(Component); if (CurrentComponent && (CurrentComponent->Mobility == EComponentMobility::Static) && CurrentComponent->VisibilityId != INDEX_NONE) { VisibilityIds.AddUnique(CurrentComponent->VisibilityId); } } } } TArray InvisibilityIds; for (int32 RemoveActorIndex = 0; RemoveActorIndex < Volume->OverrideInvisibleActors.Num(); RemoveActorIndex++) { AActor* RemoveActor = Volume->OverrideInvisibleActors[RemoveActorIndex]; if (RemoveActor) { for (UActorComponent* Component : RemoveActor->GetComponents()) { UPrimitiveComponent* RemoveComponent = Cast(Component); if (RemoveComponent && (RemoveComponent->Mobility == EComponentMobility::Static) && RemoveComponent->VisibilityId != INDEX_NONE) { InvisibilityIds.AddUnique(RemoveComponent->VisibilityId); } } } } for (int32 RemoveLevelIndex = 0; RemoveLevelIndex < Volume->OverrideInvisibleLevels.Num(); RemoveLevelIndex++) { ULevelStreaming* LevelStreaming = World->GetLevelStreamingForPackageName(Volume->OverrideInvisibleLevels[RemoveLevelIndex]); ULevel* Level; if( LevelStreaming && (Level = LevelStreaming->GetLoadedLevel()) != NULL ) { for (int32 RemoveActorIndex = 0; RemoveActorIndex < Level->Actors.Num(); RemoveActorIndex++) { AActor* RemoveActor = Level->Actors[RemoveActorIndex]; if (RemoveActor) { for (UActorComponent* Component : RemoveActor->GetComponents()) { UPrimitiveComponent* RemoveComponent = Cast(Component); if (RemoveComponent && (RemoveComponent->Mobility == EComponentMobility::Static) && RemoveComponent->VisibilityId != INDEX_NONE) { InvisibilityIds.AddUnique(RemoveComponent->VisibilityId); } } } } } } const int32 NumVisibilityIds = VisibilityIds.Num(); Swarm.WriteChannel( Channel, &NumVisibilityIds, sizeof(NumVisibilityIds) ); Swarm.WriteChannel( Channel, VisibilityIds.GetData(), VisibilityIds.Num() * VisibilityIds.GetTypeSize() ); const int32 NumInvisibilityIds = InvisibilityIds.Num(); Swarm.WriteChannel( Channel, &NumInvisibilityIds, sizeof(NumInvisibilityIds) ); Swarm.WriteChannel( Channel, InvisibilityIds.GetData(), InvisibilityIds.Num() * InvisibilityIds.GetTypeSize() ); } } const float CellSize = World->GetWorldSettings()->VisibilityCellSize; // This used to walk along Matinee sequences and write the positions for pre-computed visibility. // Leaving the swarm channel code here for fear of breaking swarm. TArray CameraTrackPositions; int32 NumCameraPositions = CameraTrackPositions.Num(); Swarm.WriteChannel( Channel, &NumCameraPositions, sizeof(NumCameraPositions) ); Swarm.WriteChannel( Channel, CameraTrackPositions.GetData(), CameraTrackPositions.Num() * CameraTrackPositions.GetTypeSize() ); } void FLightmassExporter::WriteVolumetricLightmapData( int32 Channel ) { for (int32 VolumeIndex = 0; VolumeIndex < VolumetricLightmapDensityVolumes.Num(); VolumeIndex++) { AVolumetricLightmapDensityVolume* DetailVolume = VolumetricLightmapDensityVolumes[VolumeIndex]; Lightmass::FVolumetricLightmapDensityVolumeData VolumeData; VolumeData.Bounds = FBox3f(DetailVolume->GetComponentsBoundingBox(true)); VolumeData.AllowedMipLevelRange = FIntPoint(DetailVolume->AllowedMipLevelRange.Min, DetailVolume->AllowedMipLevelRange.Max); TArray Planes4d; DetailVolume->Brush->GetSurfacePlanes(DetailVolume, Planes4d); TArray Planes; for (FPlane Plane4d : Planes4d) { Planes.Add(FPlane4f(Plane4d)); } VolumeData.NumPlanes = Planes.Num(); Swarm.WriteChannel( Channel, &VolumeData, sizeof(VolumeData) ); Swarm.WriteChannel( Channel, Planes.GetData(), Planes.Num() * Planes.GetTypeSize() ); } TArray VolumetricLightmapTaskGuidsArray; VolumetricLightmapTaskGuids.GenerateKeyArray(VolumetricLightmapTaskGuidsArray); Swarm.WriteChannel( Channel, VolumetricLightmapTaskGuidsArray.GetData(), VolumetricLightmapTaskGuidsArray.Num() * VolumetricLightmapTaskGuidsArray.GetTypeSize() ); } void FLightmassExporter::WriteLights( int32 Channel ) { // Search for the sun light component the same way as in FScene::RemoveLightSceneInfo_RenderThread. // If found, we keep the pointer to apply atmosphere transmittance to it in the light loop. const UDirectionalLightComponent* AtmosphereLights[NUM_ATMOSPHERE_LIGHTS]; float AtmosphereLightsBrightness[NUM_ATMOSPHERE_LIGHTS]; FLinearColor SunLightAtmosphereTransmittance[NUM_ATMOSPHERE_LIGHTS]; for (uint8 Index = 0; Index < NUM_ATMOSPHERE_LIGHTS; ++Index) { AtmosphereLights[Index] = nullptr; AtmosphereLightsBrightness[Index] = 0.0f; SunLightAtmosphereTransmittance[Index] = FLinearColor::White; } PRAGMA_DISABLE_DEPRECATION_WARNINGS // Compute a mapping between directional light and trnasmittance to apply. For each AtmosphereSunLightIndex, the brightest lights is kept. if (SkyAtmosphereComponent) { for (int32 LightIndex = 0; LightIndex < DirectionalLights.Num(); ++LightIndex) { const UDirectionalLightComponent* Light = DirectionalLights[LightIndex]; if (Light->IsUsedAsAtmosphereSunLight() && Light->GetColoredLightBrightness().GetLuminance() > AtmosphereLightsBrightness[Light->GetAtmosphereSunLightIndex()]) { AtmosphereLights[Light->GetAtmosphereSunLightIndex()] = Light; AtmosphereLightsBrightness[Light->GetAtmosphereSunLightIndex()] = Light->GetColoredLightBrightness().GetLuminance(); } } for (uint8 Index = 0; Index < NUM_ATMOSPHERE_LIGHTS; ++Index) { if (AtmosphereLights[Index]) { const FVector SunDirection = -AtmosphereLights[Index]->GetDirection(); if (SkyAtmosphereComponent) { FAtmosphereSetup AtmosphereSetup(*SkyAtmosphereComponent); SunLightAtmosphereTransmittance[Index] = AtmosphereSetup.GetTransmittanceAtGroundLevel(SunDirection); } break; } } } PRAGMA_ENABLE_DEPRECATION_WARNINGS // Export directional lights. for ( int32 LightIndex = 0; LightIndex < DirectionalLights.Num(); ++LightIndex ) { const UDirectionalLightComponent* Light = DirectionalLights[LightIndex]; Lightmass::FLightData LightData; Lightmass::FDirectionalLightData DirectionalData; Copy( Light, LightData ); LightData.IndirectLightingSaturation = Light->LightmassSettings.IndirectLightingSaturation; LightData.ShadowExponent = Light->LightmassSettings.ShadowExponent; LightData.ShadowResolutionScale = Light->ShadowResolutionScale; LightData.LightSourceRadius = 0; LightData.LightSourceLength = 0; // Apply atmosphere transmittance if needed. for (uint8 Index = 0; Index < NUM_ATMOSPHERE_LIGHTS; ++Index) { if (AtmosphereLights[Index] && AtmosphereLights[Index] == Light) { LightData.Color *= SunLightAtmosphereTransmittance[Index]; break; } } TArray< uint8 > LightProfileTextureData; CopyLightProfile( Light, LightData, LightProfileTextureData ); DirectionalData.LightSourceAngle = Light->LightmassSettings.LightSourceAngle * (float)PI / 180.0f; Swarm.WriteChannel( Channel, &LightData, sizeof(LightData) ); Swarm.WriteChannel( Channel, LightProfileTextureData.GetData(), LightProfileTextureData.Num() * LightProfileTextureData.GetTypeSize() ); Swarm.WriteChannel( Channel, &DirectionalData, sizeof(DirectionalData) ); UpdateExportProgress(); } // Export point lights. for ( int32 LightIndex = 0; LightIndex < PointLights.Num(); ++LightIndex ) { const UPointLightComponent* Light = PointLights[LightIndex]; Lightmass::FLightData LightData; Lightmass::FPointLightData PointData; Copy( Light, LightData ); LightData.IndirectLightingSaturation = Light->LightmassSettings.IndirectLightingSaturation; LightData.ShadowExponent = Light->LightmassSettings.ShadowExponent; LightData.ShadowResolutionScale = Light->ShadowResolutionScale; LightData.LightSourceRadius = FMath::Max( 1.0f, Light->SourceRadius ); LightData.LightSourceLength = Light->SourceLength; TArray< uint8 > LightProfileTextureData; CopyLightProfile( Light, LightData, LightProfileTextureData ); PointData.Radius = Light->AttenuationRadius; PointData.FalloffExponent = Light->LightFalloffExponent; PointData.LightTangent = FVector3f(Light->GetComponentTransform().GetUnitAxis(EAxis::Z)); Swarm.WriteChannel( Channel, &LightData, sizeof(LightData) ); Swarm.WriteChannel( Channel, LightProfileTextureData.GetData(), LightProfileTextureData.Num() * LightProfileTextureData.GetTypeSize() ); Swarm.WriteChannel( Channel, &PointData, sizeof(PointData) ); UpdateExportProgress(); } // Export spot lights. for ( int32 LightIndex = 0; LightIndex < SpotLights.Num(); ++LightIndex ) { const USpotLightComponent* Light = SpotLights[LightIndex]; Lightmass::FLightData LightData; Lightmass::FPointLightData PointData; Lightmass::FSpotLightData SpotData; Copy( Light, LightData ); LightData.IndirectLightingSaturation = Light->LightmassSettings.IndirectLightingSaturation; LightData.ShadowExponent = Light->LightmassSettings.ShadowExponent; LightData.ShadowResolutionScale = Light->ShadowResolutionScale; LightData.LightSourceRadius = FMath::Max( 1.0f, Light->SourceRadius ); LightData.LightSourceLength = Light->SourceLength; TArray< uint8 > LightProfileTextureData; CopyLightProfile( Light, LightData, LightProfileTextureData ); PointData.Radius = Light->AttenuationRadius; PointData.FalloffExponent = Light->LightFalloffExponent; PointData.LightTangent = FVector3f(Light->GetComponentTransform().GetUnitAxis(EAxis::Z)); SpotData.InnerConeAngle = Light->InnerConeAngle; SpotData.OuterConeAngle = Light->OuterConeAngle; Swarm.WriteChannel( Channel, &LightData, sizeof(LightData) ); Swarm.WriteChannel( Channel, LightProfileTextureData.GetData(), LightProfileTextureData.Num() * LightProfileTextureData.GetTypeSize() ); Swarm.WriteChannel( Channel, &PointData, sizeof(PointData) ); Swarm.WriteChannel( Channel, &SpotData, sizeof(SpotData) ); UpdateExportProgress(); } // Export rect lights. for ( int32 LightIndex = 0; LightIndex < RectLights.Num(); ++LightIndex ) { const URectLightComponent* Light = RectLights[LightIndex]; Lightmass::FLightData LightData; Copy( Light, LightData ); LightData.IndirectLightingSaturation = Light->LightmassSettings.IndirectLightingSaturation; LightData.ShadowExponent = Light->LightmassSettings.ShadowExponent; LightData.ShadowResolutionScale = Light->ShadowResolutionScale; LightData.LightSourceRadius = 0.5f * Light->SourceWidth; LightData.LightSourceLength = 0.5f * Light->SourceHeight; TArray< uint8 > LightProfileTextureData; CopyLightProfile( Light, LightData, LightProfileTextureData ); Lightmass::FPointLightData PointData; PointData.Radius = Light->AttenuationRadius; PointData.FalloffExponent = 0.0f; PointData.LightTangent = FVector3f(Light->GetComponentTransform().GetUnitAxis(EAxis::Z)); Lightmass::FRectLightData RectData = { 0, 0, FLinearColor::White }; // TODO export texture data. Below code is written for that in mind but source data doesn't contain mips. //TArray< FFloat16Color > SourceTexture; if( Light->SourceTexture ) { FTextureSource& Source = Light->SourceTexture->Source; if( Source.IsValid() ) { ETextureSourceFormat Format = Source.GetFormat(); int32 BytesPerPixel = Source.GetBytesPerPixel(); bool SRGB = Light->SourceTexture->SRGB; uint32 NumMips = Source.GetNumMips(); uint32 MipLevel = NumMips - FMath::Min( NumMips, 8u ); uint32 SizeX = FMath::Max( Source.GetSizeX() >> MipLevel, 1 ); uint32 SizeY = FMath::Max( Source.GetSizeY() >> MipLevel, 1 ); RectData.SourceTextureSizeX = SizeX; RectData.SourceTextureSizeY = SizeY; ERawImageFormat::Type RawFormat = FImageCoreUtils::ConvertToRawImageFormat(Format); if ( SizeX > 1 || SizeY > 1 ) { TArray64< uint8 > MipData; verify( Source.GetMipData( MipData, MipLevel ) ); uint8* Pixel = MipData.GetData(); uint8* PixelEnd = Pixel + MipData.Num(); while( Pixel < PixelEnd ) { FLinearColor Color = ERawImageFormat::GetOnePixelLinear(Pixel,RawFormat,SRGB); RectData.SourceTextureAvgColor += Color; Pixel += BytesPerPixel; } RectData.SourceTextureAvgColor *= 1.0f / ( SizeX * SizeY ); } } } Swarm.WriteChannel( Channel, &LightData, sizeof(LightData) ); Swarm.WriteChannel( Channel, LightProfileTextureData.GetData(), LightProfileTextureData.Num() * LightProfileTextureData.GetTypeSize() ); Swarm.WriteChannel( Channel, &PointData, sizeof(PointData) ); Swarm.WriteChannel( Channel, &RectData, sizeof(RectData) ); //Swarm.WriteChannel( Channel, SourceTexture.GetData(), SourceTexture.Num() * SourceTexture.GetTypeSize() ); UpdateExportProgress(); } // Export sky lights. for ( int32 LightIndex = 0; LightIndex < SkyLights.Num(); ++LightIndex ) { const USkyLightComponent* Light = SkyLights[LightIndex]; Lightmass::FLightData LightData; Lightmass::FSkyLightData SkyData; Copy( Light, LightData ); TArray< uint8 > LightProfileTextureData; CopyLightProfile( Light, LightData, LightProfileTextureData ); TArray RadianceMap; // Capture the scene's emissive and send it to lightmass // Note: FLightmassProcessor::InitiateExport hid all but the current lighting scenario Light->CaptureEmissiveRadianceEnvironmentCubeMap(SkyData.IrradianceEnvironmentMap, RadianceMap); VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.StaticLighting"), TEXT("bUseFilteredCubemapForSkylight"), SkyData.bUseFilteredCubemap, GLightmassIni)); SkyData.RadianceEnvironmentMapDataSize = RadianceMap.Num(); Swarm.WriteChannel( Channel, &LightData, sizeof(LightData) ); Swarm.WriteChannel( Channel, LightProfileTextureData.GetData(), LightProfileTextureData.Num() * LightProfileTextureData.GetTypeSize() ); Swarm.WriteChannel( Channel, &SkyData, sizeof(SkyData) ); Swarm.WriteChannel( Channel, RadianceMap.GetData(), RadianceMap.Num() * RadianceMap.GetTypeSize() ); UpdateExportProgress(); } } /** * Exports all UModels to secondary, persistent channels */ void FLightmassExporter::WriteModels() { for (int32 ModelIndex = 0; ModelIndex < Models.Num(); ModelIndex++) { } } /** * Exports all UStaticMeshes to secondary, persistent channels */ void FLightmassExporter::WriteStaticMeshes() { // Export geometry resources. for (int32 StaticMeshIndex = 0; StaticMeshIndex < StaticMeshes.Num() && !GEditor->GetMapBuildCancelled(); StaticMeshIndex++) { const UStaticMesh* StaticMesh = StaticMeshes[StaticMeshIndex]; Lightmass::FBaseMeshData BaseMeshData; BaseMeshData.Guid = StaticMesh->GetLightingGuid(); // create a channel name to write the mesh out to FString NewChannelName = Lightmass::CreateChannelNameWithLMExecutableHash(BaseMeshData.Guid, Lightmass::LM_STATICMESH_VERSION, LightmassExecutableHash, Lightmass::LM_STATICMESH_EXTENSION); // Warn the user if there is an invalid lightmap UV channel specified. if( StaticMesh->GetLightMapCoordinateIndex() > 0 && StaticMesh->GetRenderData() && StaticMesh->GetRenderData()->LODResources.Num() > 0 ) { const FStaticMeshLODResources& RenderData = StaticMesh->GetRenderData()->LODResources[0]; if( StaticMesh->GetLightMapCoordinateIndex() >= (int32)RenderData.VertexBuffers.StaticMeshVertexBuffer.GetNumTexCoords() ) { FMessageLog("LightingResults").Warning() ->AddToken(FUObjectToken::Create(const_cast(StaticMesh))) ->AddToken(FTextToken::Create( NSLOCTEXT("Lightmass", "LightmassError_BadLightMapCoordinateIndex", "StaticMesh has invalid LightMapCoordinateIndex.") ) ); } } // only export the static mesh if it's not currently in the cache if ((GSwarmDebugOptions.bForceContentExport == true) || (Swarm.TestChannel(*NewChannelName) < 0)) { // open the channel int32 Channel = Swarm.OpenChannel( *NewChannelName, LM_STATICMESH_CHANNEL_FLAGS ); if( Channel >= 0 ) { // write out base data Swarm.WriteChannel(Channel, &BaseMeshData, sizeof(BaseMeshData)); Lightmass::FStaticMeshData StaticMeshData; StaticMeshData.LightmapCoordinateIndex = StaticMesh->GetLightMapCoordinateIndex(); StaticMeshData.NumLODs = StaticMesh->GetRenderData()->LODResources.Num(); Swarm.WriteChannel( Channel, &StaticMeshData, sizeof(StaticMeshData) ); for (int32 LODIndex = 0; LODIndex < StaticMesh->GetRenderData()->LODResources.Num(); LODIndex++) { TArray Indices; const FStaticMeshLODResources& RenderData = StaticMesh->GetRenderData()->LODResources[LODIndex]; RenderData.IndexBuffer.GetCopy(Indices); Lightmass::FStaticMeshLODData SMLODData; SMLODData.NumElements = RenderData.Sections.Num(); SMLODData.NumTriangles = RenderData.GetNumTriangles(); SMLODData.NumIndices = Indices.Num(); // the vertex buffer could have double vertices for shadow buffer data, so we use what the render data thinks it has, not what is actually there SMLODData.NumVertices = RenderData.VertexBuffers.StaticMeshVertexBuffer.GetNumVertices(); Swarm.WriteChannel( Channel, &SMLODData, sizeof(SMLODData) ); int32 NumSections = RenderData.Sections.Num(); if( NumSections > 0 ) { TArray LMElements; LMElements.Empty(NumSections); LMElements.AddZeroed(NumSections); for (int32 SectionIndex = 0; SectionIndex < NumSections; SectionIndex++) { const FStaticMeshSection& Section = RenderData.Sections[SectionIndex]; Lightmass::FStaticMeshElementData& SMElementData = LMElements[SectionIndex]; SMElementData.FirstIndex = Section.FirstIndex; SMElementData.NumTriangles = Section.NumTriangles; SMElementData.bEnableShadowCasting = Section.bCastShadow; } Swarm.WriteChannel( Channel, (void*)(LMElements.GetData()), NumSections * sizeof(Lightmass::FStaticMeshElementData) ); } Swarm.WriteChannel( Channel, (void*)Indices.GetData(), Indices.Num() * sizeof(uint32) ); int32 VertexCount = SMLODData.NumVertices; if( VertexCount > 0 ) { TArray LMVertices; LMVertices.Empty(VertexCount); LMVertices.AddZeroed(VertexCount); for (int32 VertexIndex = 0; VertexIndex < VertexCount; VertexIndex++) { Lightmass::FStaticMeshVertex& Vertex = LMVertices[VertexIndex]; Vertex.Position = FVector4f(RenderData.VertexBuffers.PositionVertexBuffer.VertexPosition(VertexIndex), 1.0f); Vertex.TangentX = FVector4f(RenderData.VertexBuffers.StaticMeshVertexBuffer.VertexTangentX(VertexIndex)); Vertex.TangentY = FVector4f(RenderData.VertexBuffers.StaticMeshVertexBuffer.VertexTangentY(VertexIndex)); Vertex.TangentZ = FVector4f(RenderData.VertexBuffers.StaticMeshVertexBuffer.VertexTangentZ(VertexIndex)); int32 UVCount = FMath::Clamp(RenderData.VertexBuffers.StaticMeshVertexBuffer.GetNumTexCoords(), 0, MAX_TEXCOORDS); int32 UVIndex; for (UVIndex = 0; UVIndex < UVCount; UVIndex++) { Vertex.UVs[UVIndex] = RenderData.VertexBuffers.StaticMeshVertexBuffer.GetVertexUV(VertexIndex, UVIndex); } for (; UVIndex < MAX_TEXCOORDS; UVIndex++) { Vertex.UVs[UVIndex] = FVector2f::ZeroVector; } } Swarm.WriteChannel( Channel, (void*)(LMVertices.GetData()), LMVertices.Num() * sizeof(Lightmass::FStaticMeshVertex)); } } // close the channel, the whole mesh is now exported Swarm.CloseChannel(Channel); } else { UE_LOG(LogLightmassSolver, Log, TEXT("Error, OpenChannel failed to open %s with error code %d"), *NewChannelName, Channel ); } } UpdateExportProgress(); } } void FLightmassExporter::GetMaterialHash(const UMaterialInterface* Material, FSHAHash& OutHash) { FSHA1 HashState; TArray MaterialGuids; Material->GetLightingGuidChain(true, MaterialGuids); MaterialGuids.Sort(); FGuid LastGuid; for (const FGuid& MaterialGuid : MaterialGuids) { if (MaterialGuid != LastGuid) { HashState.Update((const uint8*)&MaterialGuid, sizeof(MaterialGuid)); LastGuid = MaterialGuid; } } if (Substrate::IsSubstrateEnabled()) { uint32 LightmassSubstrateVersion = 0XFA1081D0; // This can be change when the code/logic for converting material to Substrate for lightmap has changed. HashState.Update((const uint8*)&LightmassSubstrateVersion, sizeof(LightmassSubstrateVersion)); } HashState.Final(); HashState.GetHash(&OutHash.Hash[0]); } void FLightmassExporter::BuildMaterialMap(UMaterialInterface* Material) { if (ensure(Material)) { FSHAHash MaterialHash; GetMaterialHash(Material, MaterialHash); // create a channel name to write the material out to FString NewChannelName = Lightmass::CreateChannelNameWithLMExecutableHash(MaterialHash, Lightmass::LM_MATERIAL_VERSION, LightmassExecutableHash, Lightmass::LM_MATERIAL_EXTENSION); // only export the material if it's not currently in the cache int32 ErrorCode; if ( GSwarmDebugOptions.bForceContentExport == true ) { // If we're forcing export of content, pretend we didn't find it ErrorCode = NSwarm::SWARM_ERROR_FILE_FOUND_NOT; } else { // Otherwise, test the channel ErrorCode = Swarm.TestChannel(*NewChannelName); } if ( ErrorCode != NSwarm::SWARM_SUCCESS ) { if( ErrorCode == NSwarm::SWARM_ERROR_FILE_FOUND_NOT ) { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.NormalMapsForStaticLighting")); const bool bUseNormalMapsForLighting = CVar->GetValueOnGameThread() != 0; // Only generate normal maps if we'll actually need them for lighting MaterialRenderer.BeginGenerateMaterialData(Material, bUseNormalMapsForLighting, NewChannelName, MaterialExportData); } else { UE_LOG(LogLightmassSolver, Warning, TEXT("Error in TestChannel() for %s: %s"), *(MaterialHash.ToString()), *(Material->GetPathName())); } } } } void FLightmassExporter::BlockOnShaderCompilation() { // Block until async shader compiling is finished before we try to use the shaders for exporting // The code is structured to only block once for all materials, so that shader compiling is able to utilize many cores GShaderCompilingManager->FinishAllCompilation(); } void FLightmassExporter::ExportMaterial(UMaterialInterface* Material, const FLightmassMaterialExportSettings& ExportSettings) { FMaterialExportDataEntry* ExportEntry = MaterialExportData.Find(Material); // Only create the Swarm channel if there is something to export if (ensure(Material) && ExportEntry) { Lightmass::FBaseMaterialData BaseMaterialData; BaseMaterialData.Guid = Material->GetLightingGuid(); // Generate the required information Lightmass::FMaterialData MaterialData; FMemory::Memzero(&MaterialData,sizeof(MaterialData)); UMaterial* BaseMaterial = Material->GetMaterial(); MaterialData.bTwoSided = (uint32)Material->IsTwoSided(); MaterialData.bIsThinSurface = (uint32)Material->IsThinSurface(); MaterialData.EmissiveBoost = Material->GetEmissiveBoost(); MaterialData.DiffuseBoost = Material->GetDiffuseBoost(); TArray MaterialEmissive; TArray MaterialDiffuse; TArray MaterialTransmission; TArray MaterialNormal; if (MaterialRenderer.GenerateMaterialData( World->Scene, *Material, ExportSettings, MaterialData, *ExportEntry, MaterialDiffuse, MaterialEmissive, MaterialTransmission, MaterialNormal)) { // open the channel int32 Channel = Swarm.OpenChannel( *(ExportEntry->ChannelName), LM_MATERIAL_CHANNEL_FLAGS ); if( Channel >= 0 ) { // write out base data Swarm.WriteChannel(Channel, &BaseMaterialData, sizeof(BaseMaterialData)); // the material data Swarm.WriteChannel(Channel, &MaterialData, sizeof(MaterialData)); // Write each array of data uint8* OutData; int32 OutSize; OutSize = FMath::Square(MaterialData.EmissiveSize) * sizeof(FFloat16Color); if (OutSize > 0) { OutData = (uint8*)(MaterialEmissive.GetData()); Swarm.WriteChannel(Channel, OutData, OutSize); } OutSize = FMath::Square(MaterialData.DiffuseSize) * sizeof(FFloat16Color); if (OutSize > 0) { OutData = (uint8*)(MaterialDiffuse.GetData()); Swarm.WriteChannel(Channel, OutData, OutSize); } OutSize = FMath::Square(MaterialData.TransmissionSize) * sizeof(FFloat16Color); if (OutSize > 0) { OutData = (uint8*)(MaterialTransmission.GetData()); Swarm.WriteChannel(Channel, OutData, OutSize); } OutSize = FMath::Square(MaterialData.NormalSize) * sizeof(FFloat16Color); if (OutSize > 0) { OutData = (uint8*)(MaterialNormal.GetData()); Swarm.WriteChannel(Channel, OutData, OutSize); } OpenedMaterialExportChannels.Add(Channel); } else { UE_LOG(LogLightmassSolver, Warning, TEXT("Failed to open channel for material data for %s: %s"), *(Material->GetLightingGuid().ToString()), *(Material->GetPathName())); } } else { UE_LOG(LogLightmassSolver, Warning, TEXT("Failed to generate material data for %s: %s"), *(Material->GetLightingGuid().ToString()), *(Material->GetPathName())); } } UpdateExportProgress(); } void FLightmassExporter::WriteBaseMeshInstanceData( int32 Channel, int32 MeshIndex, const FStaticLightingMesh* Mesh, TArray& MaterialElementData ) { Lightmass::FStaticLightingMeshInstanceData MeshInstanceData; FMemory::Memzero(&MeshInstanceData,sizeof(MeshInstanceData)); MeshInstanceData.Guid = Mesh->Guid; MeshInstanceData.NumTriangles = Mesh->NumTriangles; MeshInstanceData.NumShadingTriangles = Mesh->NumShadingTriangles; MeshInstanceData.NumVertices = Mesh->NumVertices; MeshInstanceData.NumShadingVertices = Mesh->NumShadingVertices; MeshInstanceData.MeshIndex = MeshIndex; MeshInstanceData.LevelGuid = LightingContext.GetPersistentLevelGuid(); check(Mesh->Component); bool bFoundLevel = false; AActor* ComponentOwner = Mesh->Component->GetOwner(); if (ComponentOwner && ComponentOwner->GetLevel()) { MeshInstanceData.LevelGuid = LightingContext.GetLevelGuidForActor(ComponentOwner); bFoundLevel = true; } else if (Mesh->Component->IsA(UModelComponent::StaticClass())) { const UModelComponent* ModelComponent = CastChecked(Mesh->Component); for (int32 LevelIndex = 0; LevelIndex < World->GetNumLevels(); LevelIndex++) { if (ModelComponent->GetModel() == World->GetLevel(LevelIndex)->Model) { MeshInstanceData.LevelGuid = LightingContext.GetLevelGuidForLevel(World->GetLevel(LevelIndex)); bFoundLevel = true; break; } } } if (!bFoundLevel) { UE_LOG(LogLightmassSolver, Warning, TEXT("Couldn't determine level for component %s during Lightmass export, it will be considered in the persistent level!"), *Mesh->Component->GetPathName()); } MeshInstanceData.LightingFlags = 0; MeshInstanceData.LightingFlags |= Mesh->bCastShadow ? Lightmass::GI_INSTANCE_CASTSHADOW : 0; MeshInstanceData.LightingFlags |= Mesh->bTwoSidedMaterial ? Lightmass::GI_INSTANCE_TWOSIDED : 0; MeshInstanceData.bCastShadowAsTwoSided = Mesh->Component->bCastShadowAsTwoSided; MeshInstanceData.bMovable = (Mesh->Component->Mobility != EComponentMobility::Static); MeshInstanceData.NumRelevantLights = Mesh->RelevantLightsGuid.Num(); MeshInstanceData.BoundingBox = FBox3f(Mesh->BoundingBox); Swarm.WriteChannel( Channel, &MeshInstanceData, sizeof(MeshInstanceData) ); const uint32 LightGuidsSize = Mesh->RelevantLightsGuid.Num() * sizeof(FGuid); if( LightGuidsSize > 0 ) { FGuid* LightGuids = (FGuid*)FMemory::Malloc(LightGuidsSize); for( int32 LightIdx=0; LightIdx < Mesh->RelevantLightsGuid.Num(); LightIdx++ ) { LightGuids[LightIdx] = Mesh->RelevantLightsGuid[LightIdx]; } Swarm.WriteChannel( Channel, LightGuids, LightGuidsSize ); FMemory::Free( LightGuids ); } const int32 NumVisibilitiyIds = Mesh->VisibilityIds.Num(); Swarm.WriteChannel(Channel, &NumVisibilitiyIds, sizeof(NumVisibilitiyIds)); Swarm.WriteChannel(Channel, Mesh->VisibilityIds.GetData(), Mesh->VisibilityIds.Num() * Mesh->VisibilityIds.GetTypeSize()); // Always need to have at least one material if (MaterialElementData.Num() == 0) { Lightmass::FMaterialElementData DefaultData; GetMaterialHash(UMaterial::GetDefaultMaterial(MD_Surface), DefaultData.MaterialHash); MaterialElementData.Add(DefaultData); } // Write out the materials used by this mesh... const int32 NumMaterialElements = MaterialElementData.Num(); Swarm.WriteChannel(Channel, &NumMaterialElements, sizeof(NumMaterialElements)); for (int32 MtrlIdx = 0; MtrlIdx < NumMaterialElements; MtrlIdx++) { Swarm.WriteChannel(Channel, &(MaterialElementData[MtrlIdx]), sizeof(Lightmass::FMaterialElementData)); } } void FLightmassExporter::WriteBaseMappingData( int32 Channel, const FStaticLightingMapping* Mapping ) { Lightmass::FStaticLightingMappingData MappingData; FMemory::Memzero(&MappingData,sizeof(MappingData)); MappingData.Guid = Mapping->Mesh->Guid; MappingData.StaticLightingMeshInstance = Mapping->Mesh->SourceMeshGuid; Swarm.WriteChannel( Channel, &MappingData, sizeof(MappingData) ); } void FLightmassExporter::WriteBaseTextureMappingData( int32 Channel, const FStaticLightingTextureMapping* TextureMapping ) { WriteBaseMappingData( Channel, TextureMapping ); Lightmass::FStaticLightingTextureMappingData TextureMappingData; FMemory::Memzero(&TextureMappingData,sizeof(TextureMappingData)); check(TextureMapping->SizeX > 0 && TextureMapping->SizeY > 0); TextureMappingData.SizeX = TextureMapping->SizeX; TextureMappingData.SizeY = TextureMapping->SizeY; TextureMappingData.LightmapTextureCoordinateIndex = TextureMapping->LightmapTextureCoordinateIndex; TextureMappingData.bBilinearFilter = TextureMapping->bBilinearFilter; Swarm.WriteChannel( Channel, &TextureMappingData, sizeof(TextureMappingData) ); } void FLightmassExporter::WriteLandscapeMapping(int32 Channel, const class FLandscapeStaticLightingTextureMapping* LandscapeMapping) { WriteBaseTextureMappingData(Channel, (const FStaticLightingTextureMapping*)LandscapeMapping); } struct FMeshAndLODId { int32 MeshIndex; int32 LODIndex; }; void FLightmassExporter::WriteMeshInstances( int32 Channel ) { // initially come up with a unique ID for each component TMap ComponentToIDMap; int32 NextId = 0; for( int32 MeshIdx=0; MeshIdx < StaticMeshLightingMeshes.Num(); MeshIdx++ ) { const FStaticMeshStaticLightingMesh* SMLightingMesh = StaticMeshLightingMeshes[MeshIdx]; const UStaticMesh* StaticMesh = SMLightingMesh->StaticMesh; if (StaticMesh) { const UStaticMeshComponent* Primitive = SMLightingMesh->Primitive; if (Primitive) { // All FStaticMeshStaticLightingMesh's in the OtherMeshLODs array need to get the same MeshIndex but different LODIndex // So that they won't shadow each other in Lightmass. HLODs are forced as new meshes and rely on custom handling if (SMLightingMesh->HLODTreeIndex) { FMeshAndLODId NewId; NewId.MeshIndex = NextId++; NewId.LODIndex = 0; ComponentToIDMap.Add(Primitive, NewId); } else if (SMLightingMesh->OtherMeshLODs.Num() > 0) { FMeshAndLODId* ExistingLODId = NULL; int32 LargestLODIndex = INDEX_NONE; for (int32 OtherLODIndex = 0; OtherLODIndex < SMLightingMesh->OtherMeshLODs.Num(); OtherLODIndex++) { FStaticLightingMesh* OtherLOD = SMLightingMesh->OtherMeshLODs[OtherLODIndex]; if (OtherLOD->Component) { FMeshAndLODId* CurrentLODId = ComponentToIDMap.Find(OtherLOD->Component); // Find the mesh with the largest index if (CurrentLODId && CurrentLODId->LODIndex > LargestLODIndex) { ExistingLODId = CurrentLODId; LargestLODIndex = CurrentLODId->LODIndex; } } } if (ExistingLODId) { FMeshAndLODId NewId; // Reuse the mesh index from another LOD NewId.MeshIndex = ExistingLODId->MeshIndex; // Assign a new unique LOD index NewId.LODIndex = ExistingLODId->LODIndex + 1; ComponentToIDMap.Add(Primitive, NewId); } else { FMeshAndLODId NewId; NewId.MeshIndex = NextId++; NewId.LODIndex = 0; ComponentToIDMap.Add(Primitive, NewId); } } else { FMeshAndLODId NewId; NewId.MeshIndex = NextId++; NewId.LODIndex = 0; ComponentToIDMap.Add(Primitive, NewId); } } } } // static mesh instance meshes for( int32 MeshIdx=0; MeshIdx < StaticMeshLightingMeshes.Num(); MeshIdx++ ) { const FStaticMeshStaticLightingMesh* SMLightingMesh = StaticMeshLightingMeshes[MeshIdx]; FMeshAndLODId* MeshId = NULL; // Collect the material guids for each element TArray MaterialElementData; const UStaticMesh* StaticMesh = SMLightingMesh->StaticMesh; check(StaticMesh); const UStaticMeshComponent* Primitive = SMLightingMesh->Primitive; if (Primitive) { // get the meshindex from the component MeshId = ComponentToIDMap.Find(Primitive); if (StaticMesh->GetRenderData() && SMLightingMesh->LODIndex < StaticMesh->GetRenderData()->LODResources.Num()) { const FStaticMeshLODResources& LODRenderData = StaticMesh->GetRenderData()->LODResources[SMLightingMesh->LODIndex]; for (int32 SectionIndex = 0; SectionIndex < LODRenderData.Sections.Num(); SectionIndex++) { const FStaticMeshSection& Section = LODRenderData.Sections[ SectionIndex ]; UMaterialInterface* Material = Primitive->GetMaterial(Section.MaterialIndex); if (Material == NULL) { Material = UMaterial::GetDefaultMaterial(MD_Surface); } Lightmass::FMaterialElementData NewElementData; GetMaterialHash(Material, NewElementData.MaterialHash); NewElementData.bUseTwoSidedLighting = Primitive->LightmassSettings.bUseTwoSidedLighting; NewElementData.bShadowIndirectOnly = Primitive->LightmassSettings.bShadowIndirectOnly; NewElementData.bUseEmissiveForStaticLighting = Primitive->LightmassSettings.bUseEmissiveForStaticLighting; NewElementData.bUseVertexNormalForHemisphereGather = Primitive->LightmassSettings.bUseVertexNormalForHemisphereGather; // Combine primitive and level boost settings so we don't have to send the level settings over to Lightmass NewElementData.EmissiveLightFalloffExponent = Primitive->LightmassSettings.EmissiveLightFalloffExponent; NewElementData.EmissiveLightExplicitInfluenceRadius = Primitive->LightmassSettings.EmissiveLightExplicitInfluenceRadius; NewElementData.EmissiveBoost = Primitive->GetEmissiveBoost(SectionIndex) * LevelSettings.EmissiveBoost; NewElementData.DiffuseBoost = Primitive->GetDiffuseBoost(SectionIndex) * LevelSettings.DiffuseBoost; NewElementData.FullyOccludedSamplesFraction = Primitive->LightmassSettings.FullyOccludedSamplesFraction; MaterialElementData.Add(NewElementData); } } } WriteBaseMeshInstanceData( Channel, MeshId ? MeshId->MeshIndex : INDEX_NONE, SMLightingMesh, MaterialElementData ); Lightmass::FStaticMeshStaticLightingMeshData SMInstanceMeshData; FMemory::Memzero(&SMInstanceMeshData,sizeof(SMInstanceMeshData)); // Store HLOD data in upper 16 bits SMInstanceMeshData.EncodedLODIndices = SMLightingMesh->LODIndex & 0xFFFF; SMInstanceMeshData.EncodedLODIndices |= (SMLightingMesh->HLODTreeIndex & 0xFFFF) << 16; SMInstanceMeshData.EncodedHLODRange = SMLightingMesh->HLODChildStartIndex & 0xFFFF; SMInstanceMeshData.EncodedHLODRange |= (SMLightingMesh->HLODChildEndIndex & 0xFFFF) << 16; SMInstanceMeshData.LocalToWorld = FMatrix44f(SMLightingMesh->LocalToWorld); // LWC_TODO: Precision loss relevant here? SMInstanceMeshData.bReverseWinding = SMLightingMesh->bReverseWinding; SMInstanceMeshData.bShouldSelfShadow = true; SMInstanceMeshData.StaticMeshGuid = SMLightingMesh->StaticMesh->GetLightingGuid(); const FSplineMeshParams* SplineParams = SMLightingMesh->GetSplineParameters(); if (SplineParams && StaticMesh) { FBoxSphereBounds MeshBounds = StaticMesh->GetBounds(); const USplineMeshComponent* SplineComponent = CastChecked(SMLightingMesh->Component); SMInstanceMeshData.bIsSplineMesh = true; Copy(*SplineParams, SMInstanceMeshData.SplineParameters); SMInstanceMeshData.SplineParameters.SplineUpDir = FVector3f(SplineComponent->SplineUpDir); SMInstanceMeshData.SplineParameters.bSmoothInterpRollScale = SplineComponent->bSmoothInterpRollScale; if (FMath::IsNearlyEqual(SplineComponent->SplineBoundaryMin, SplineComponent->SplineBoundaryMax)) { // set ranges according to the extents of the mesh SMInstanceMeshData.SplineParameters.MeshMinZ = MeshBounds.Origin[SplineComponent->ForwardAxis] - MeshBounds.BoxExtent[SplineComponent->ForwardAxis]; SMInstanceMeshData.SplineParameters.MeshRangeZ = 2.f * MeshBounds.BoxExtent[SplineComponent->ForwardAxis]; } else { // set ranges according to the custom boundary min/max SMInstanceMeshData.SplineParameters.MeshMinZ = SplineComponent->SplineBoundaryMin; SMInstanceMeshData.SplineParameters.MeshRangeZ = SplineComponent->SplineBoundaryMax - SplineComponent->SplineBoundaryMin; } SMInstanceMeshData.SplineParameters.ForwardAxis = (Lightmass::ESplineMeshAxis::Type)SplineComponent->ForwardAxis.GetValue(); } else { SMInstanceMeshData.bIsSplineMesh = false; FMemory::Memzero(&SMInstanceMeshData.SplineParameters, sizeof(SMInstanceMeshData.SplineParameters)); } Swarm.WriteChannel( Channel, &SMInstanceMeshData, sizeof(SMInstanceMeshData) ); UpdateExportProgress(); } } void FLightmassExporter::WriteLandscapeInstances( int32 Channel ) { // landscape instance meshes for (int32 LandscapeIdx = 0; LandscapeIdx < LandscapeLightingMeshes.Num(); LandscapeIdx++) { const FLandscapeStaticLightingMesh* LandscapeLightingMesh = LandscapeLightingMeshes[LandscapeIdx]; // Collect the material guids for each element TArray MaterialElementData; const ULandscapeComponent* LandscapeComp = LandscapeLightingMesh->LandscapeComponent; if (LandscapeComp && LandscapeComp->GetLandscapeProxy()) { UMaterialInterface* Material = LandscapeComp->GetMaterialInstance(0, false); if (!Material) { Material = UMaterial::GetDefaultMaterial(MD_Surface); } Lightmass::FMaterialElementData NewElementData; GetMaterialHash(Material, NewElementData.MaterialHash); FLightmassPrimitiveSettings& LMSetting = LandscapeComp->GetLandscapeProxy()->LightmassSettings; NewElementData.bUseTwoSidedLighting = LMSetting.bUseTwoSidedLighting; NewElementData.bShadowIndirectOnly = LMSetting.bShadowIndirectOnly; NewElementData.bUseEmissiveForStaticLighting = LMSetting.bUseEmissiveForStaticLighting; NewElementData.bUseVertexNormalForHemisphereGather = LMSetting.bUseVertexNormalForHemisphereGather; // Combine primitive and level boost settings so we don't have to send the level settings over to Lightmass NewElementData.EmissiveLightFalloffExponent = LMSetting.EmissiveLightFalloffExponent; NewElementData.EmissiveLightExplicitInfluenceRadius = LMSetting.EmissiveLightExplicitInfluenceRadius; NewElementData.EmissiveBoost = LandscapeComp->GetEmissiveBoost(0) * LevelSettings.EmissiveBoost; NewElementData.DiffuseBoost = LandscapeComp->GetDiffuseBoost(0) * LevelSettings.DiffuseBoost; NewElementData.FullyOccludedSamplesFraction = LMSetting.FullyOccludedSamplesFraction; MaterialElementData.Add(NewElementData); } WriteBaseMeshInstanceData(Channel, INDEX_NONE, LandscapeLightingMesh, MaterialElementData); Lightmass::FLandscapeStaticLightingMeshData LandscapeInstanceMeshData; FMemory::Memzero(&LandscapeInstanceMeshData,sizeof(LandscapeInstanceMeshData)); LandscapeInstanceMeshData.LocalToWorld = FMatrix44f(LandscapeLightingMesh->LocalToWorld.ToMatrixWithScale()); // LWC_TODO: Precision loss relevant here? LandscapeInstanceMeshData.ComponentSizeQuads = LandscapeLightingMesh->ComponentSizeQuads; LandscapeInstanceMeshData.LightMapRatio = LandscapeLightingMesh->LightMapRatio; LandscapeInstanceMeshData.ExpandQuadsX = LandscapeLightingMesh->ExpandQuadsX; LandscapeInstanceMeshData.ExpandQuadsY = LandscapeLightingMesh->ExpandQuadsY; Swarm.WriteChannel( Channel, &LandscapeInstanceMeshData, sizeof(LandscapeInstanceMeshData) ); // write height map data uint8* OutData; int32 OutSize; OutSize = LandscapeLightingMesh->HeightData.Num() * sizeof(FColor); if (OutSize > 0) { OutData = (uint8*)(LandscapeLightingMesh->HeightData.GetData()); Swarm.WriteChannel(Channel, OutData, OutSize); } UpdateExportProgress(); } } struct FLightmassMaterialPair { FLightmassMaterialPair(int32 InLightmassSettingsIndex, UMaterialInterface* InMaterial) : LightmassSettingsIndex(InLightmassSettingsIndex) , Material(InMaterial) { } /** Index into the Model's LightmassSettings array for this triangle */ int32 LightmassSettingsIndex; /** Materials used by this triangle */ UMaterialInterface* Material; bool operator==( const FLightmassMaterialPair& Other ) const { return LightmassSettingsIndex == Other.LightmassSettingsIndex && Material == Other.Material; } }; void FLightmassExporter::WriteMappings( int32 Channel ) { // bsp mappings for( int32 MappingIdx=0; MappingIdx < BSPSurfaceMappings.Num() && !GEditor->GetMapBuildCancelled(); MappingIdx++ ) { const FBSPSurfaceStaticLighting* BSPMapping = BSPSurfaceMappings[MappingIdx]; TArray MaterialElementData; const UModel* Model = BSPMapping->GetModel(); check(Model); // make a list of the used lightmass settings by this NodeGroup and a mapping from // each triangle into this array TArray LocalLightmassSettings; TArray LocalPerTriangleLightmassSettings; // go through each triangle, looking for unique settings, and remapping each triangle int32 NumTriangles = BSPMapping->NodeGroup->TriangleSurfaceMap.Num(); LocalPerTriangleLightmassSettings.Empty(NumTriangles); LocalPerTriangleLightmassSettings.AddUninitialized(NumTriangles); for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; TriangleIndex++) { const FBspSurf& Surf = Model->Surfs[BSPMapping->NodeGroup->TriangleSurfaceMap[TriangleIndex]]; LocalPerTriangleLightmassSettings[TriangleIndex] = LocalLightmassSettings.AddUnique(FLightmassMaterialPair(Surf.iLightmassIndex, Surf.Material)); } // now for each used setting, export it for (int32 SettingIndex = 0; SettingIndex < LocalLightmassSettings.Num(); SettingIndex++) { const FLightmassMaterialPair& Pair = LocalLightmassSettings[SettingIndex]; UMaterialInterface* Material = Pair.Material; if (Material == NULL) { Material = UMaterial::GetDefaultMaterial(MD_Surface); } check(Material); // get the settings from the model const FLightmassPrimitiveSettings& PrimitiveSettings = Model->LightmassSettings[Pair.LightmassSettingsIndex]; Lightmass::FMaterialElementData TempData; GetMaterialHash(Material, TempData.MaterialHash); TempData.bUseTwoSidedLighting = PrimitiveSettings.bUseTwoSidedLighting; TempData.bShadowIndirectOnly = PrimitiveSettings.bShadowIndirectOnly; TempData.bUseEmissiveForStaticLighting = PrimitiveSettings.bUseEmissiveForStaticLighting; TempData.bUseVertexNormalForHemisphereGather = PrimitiveSettings.bUseVertexNormalForHemisphereGather; TempData.EmissiveLightFalloffExponent = PrimitiveSettings.EmissiveLightFalloffExponent; TempData.EmissiveLightExplicitInfluenceRadius = PrimitiveSettings.EmissiveLightExplicitInfluenceRadius; TempData.EmissiveBoost = PrimitiveSettings.EmissiveBoost * LevelSettings.EmissiveBoost; TempData.DiffuseBoost = PrimitiveSettings.DiffuseBoost * LevelSettings.DiffuseBoost; TempData.FullyOccludedSamplesFraction = PrimitiveSettings.FullyOccludedSamplesFraction; MaterialElementData.Add(TempData); } WriteBaseMeshInstanceData(Channel, INDEX_NONE, BSPMapping, MaterialElementData); WriteBaseTextureMappingData( Channel, BSPMapping ); Lightmass::FBSPSurfaceStaticLightingData BSPSurfaceMappingData; BSPSurfaceMappingData.TangentX = BSPMapping->NodeGroup->TangentX; BSPSurfaceMappingData.TangentY = BSPMapping->NodeGroup->TangentY; BSPSurfaceMappingData.TangentZ = BSPMapping->NodeGroup->TangentZ; BSPSurfaceMappingData.MapToWorld = FMatrix44f(BSPMapping->NodeGroup->MapToWorld); // LWC_TODO: Precision loss? BSPSurfaceMappingData.WorldToMap = FMatrix44f(BSPMapping->NodeGroup->WorldToMap); // LWC_TODO: Precision loss? Swarm.WriteChannel( Channel, &BSPSurfaceMappingData, sizeof(BSPSurfaceMappingData) ); const uint32 VertexDataSize = BSPMapping->NodeGroup->Vertices.Num() * sizeof(Lightmass::FStaticLightingVertexData); if( VertexDataSize > 0 ) { Lightmass::FStaticLightingVertexData* VertexData = (Lightmass::FStaticLightingVertexData*)FMemory::Malloc(VertexDataSize); for( int32 VertIdx=0; VertIdx < BSPMapping->NodeGroup->Vertices.Num(); VertIdx++ ) { const FStaticLightingVertex& SrcVertex = BSPMapping->NodeGroup->Vertices[VertIdx]; Lightmass::FStaticLightingVertexData& DstVertex = VertexData[VertIdx]; // LWC_TODO: precision loss DstVertex.WorldPosition = (FVector3f)SrcVertex.WorldPosition; DstVertex.WorldTangentX = (FVector3f)SrcVertex.WorldTangentX; DstVertex.WorldTangentY = (FVector3f)SrcVertex.WorldTangentY; DstVertex.WorldTangentZ = (FVector3f)SrcVertex.WorldTangentZ; for( int32 CoordIdx=0; CoordIdx < Lightmass::MAX_TEXCOORDS; CoordIdx++ ) { DstVertex.TextureCoordinates[CoordIdx] = FVector2f(SrcVertex.TextureCoordinates[CoordIdx]); // LWC_TODO: Precision loss } } Swarm.WriteChannel( Channel, VertexData, VertexDataSize ); FMemory::Free( VertexData ); } if( BSPMapping->NodeGroup->TriangleVertexIndices.Num() > 0 ) { Swarm.WriteChannel( Channel, (void*)BSPMapping->NodeGroup->TriangleVertexIndices.GetData(), BSPMapping->NodeGroup->TriangleVertexIndices.Num() * sizeof(int32) ); } Swarm.WriteChannel(Channel, LocalPerTriangleLightmassSettings.GetData(), LocalPerTriangleLightmassSettings.Num() * sizeof(int32)); UpdateExportProgress(); } // static mesh texture mappings for( int32 MappingIdx=0; MappingIdx < StaticMeshTextureMappings.Num(); MappingIdx++ ) { const FStaticMeshStaticLightingTextureMapping* SMTextureMapping = StaticMeshTextureMappings[MappingIdx]; WriteBaseTextureMappingData( Channel, SMTextureMapping ); UpdateExportProgress(); } // landscape surface mappings for (int32 MappingIdx = 0; MappingIdx < LandscapeTextureMappings.Num(); MappingIdx++) { const FLandscapeStaticLightingTextureMapping* LandscapeMapping = LandscapeTextureMappings[MappingIdx]; WriteLandscapeMapping(Channel, LandscapeMapping); UpdateExportProgress(); } for (int32 MappingIdx = 0; MappingIdx < VolumeMappings.Num(); MappingIdx++) { const FStaticLightingGlobalVolumeMapping* VolumeMapping = VolumeMappings[MappingIdx]; WriteBaseTextureMappingData( Channel, VolumeMapping ); UpdateExportProgress(); } for (int32 MappingIdx = 0; MappingIdx < LandscapeVolumeMappings.Num(); MappingIdx++) { const FLandscapeStaticLightingGlobalVolumeMapping* VolumeMapping = LandscapeVolumeMappings[MappingIdx]; WriteLandscapeMapping( Channel, VolumeMapping ); UpdateExportProgress(); } } /** Finds the GUID of the mapping that is being debugged. */ bool FLightmassExporter::FindDebugMapping(FGuid& DebugMappingGuid) { const FStaticLightingMapping* FoundDebugMapping = NULL; // Only BSP texture, static mesh vertex and texture lightmaps supported for now. for( int32 MappingIdx=0; MappingIdx < BSPSurfaceMappings.Num(); MappingIdx++ ) { const FBSPSurfaceStaticLighting* BSPMapping = BSPSurfaceMappings[MappingIdx]; if (BSPMapping->DebugThisMapping()) { // Only one mapping should be setup for debugging check(!FoundDebugMapping); FoundDebugMapping = BSPMapping; } } for( int32 MappingIdx=0; MappingIdx < StaticMeshTextureMappings.Num(); MappingIdx++ ) { const FStaticMeshStaticLightingTextureMapping* SMTextureMapping = StaticMeshTextureMappings[MappingIdx]; if (SMTextureMapping->DebugThisMapping()) { // Only one mapping should be setup for debugging check(!FoundDebugMapping); FoundDebugMapping = SMTextureMapping; } } if (FoundDebugMapping) { DebugMappingGuid = FoundDebugMapping->GetLightingGuid(); return true; } else { return false; } } void FLightmassExporter::SetVolumetricLightmapSettings(Lightmass::FVolumetricLightmapSettings& OutSettings) { FBox CombinedImportanceVolume(ForceInit); for (int32 i = 0; i < ImportanceVolumes.Num(); i++) { CombinedImportanceVolume += ImportanceVolumes[i]; } const FVector ImportanceExtent = CombinedImportanceVolume.GetExtent(); // Guarantee cube voxels. // This means some parts of the volumetric lightmap volume will be outside the lightmass importance volume. // We prevent refinement outside of importance volumes in FStaticLightingSystem::ShouldRefineVoxel const float MaxExtent = FMath::Max(ImportanceExtent.X, FMath::Max(ImportanceExtent.Y, ImportanceExtent.Z)); OutSettings.VolumeMin = FVector3f(CombinedImportanceVolume.Min); //LWC_TODO: Precision loss const FVector RequiredVolumeSize = FVector(MaxExtent * 2); VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.VolumetricLightmaps"), TEXT("BrickSize"), OutSettings.BrickSize, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.VolumetricLightmaps"), TEXT("MaxRefinementLevels"), OutSettings.MaxRefinementLevels, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.VolumetricLightmaps"), TEXT("VoxelizationCellExpansionForSurfaceGeometry"), OutSettings.VoxelizationCellExpansionForSurfaceGeometry, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.VolumetricLightmaps"), TEXT("VoxelizationCellExpansionForVolumeGeometry"), OutSettings.VoxelizationCellExpansionForVolumeGeometry, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.VolumetricLightmaps"), TEXT("VoxelizationCellExpansionForLights"), OutSettings.VoxelizationCellExpansionForLights, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.VolumetricLightmaps"), TEXT("MinBrickError"), OutSettings.MinBrickError, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.VolumetricLightmaps"), TEXT("SurfaceLightmapMinTexelsPerVoxelAxis"), OutSettings.SurfaceLightmapMinTexelsPerVoxelAxis, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.VolumetricLightmaps"), TEXT("bCullBricksBelowLandscape"), OutSettings.bCullBricksBelowLandscape, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.VolumetricLightmaps"), TEXT("LightBrightnessSubdivideThreshold"), OutSettings.LightBrightnessSubdivideThreshold, GLightmassIni)); const FLightmassWorldInfoSettings& WorldInfoSettings = World->GetWorldSettings()->LightmassSettings; float Smoothing = FMath::Clamp(WorldInfoSettings.VolumetricLightmapSphericalHarmonicSmoothing, SMALL_NUMBER, 1000.0f); OutSettings.WindowingTargetLaplacian = 1.0f / Smoothing; OutSettings.BrickSize = FMath::RoundUpToPowerOfTwo(OutSettings.BrickSize); OutSettings.MaxRefinementLevels = FMath::Clamp(OutSettings.MaxRefinementLevels, 1, 6); OutSettings.VoxelizationCellExpansionForSurfaceGeometry = FMath::Max(OutSettings.VoxelizationCellExpansionForSurfaceGeometry, 0.0f); OutSettings.VoxelizationCellExpansionForVolumeGeometry = FMath::Max(OutSettings.VoxelizationCellExpansionForVolumeGeometry, 0.0f); OutSettings.VoxelizationCellExpansionForLights = FMath::Max(OutSettings.VoxelizationCellExpansionForLights, 0.0f); const float TargetDetailCellSize = WorldInfoSettings.VolumetricLightmapDetailCellSize; FIntVector FullGridSize( FMath::TruncToInt(2 * ImportanceExtent.X / TargetDetailCellSize) + 1, FMath::TruncToInt(2 * ImportanceExtent.Y / TargetDetailCellSize) + 1, FMath::TruncToInt(2 * ImportanceExtent.Z / TargetDetailCellSize) + 1); // Make sure size of indirection texture does not exceed INT_MAX const int32 MaxGridDimension = FMath::Pow(2048000000 / 4, 1.0f / 3) * OutSettings.BrickSize; if (FullGridSize.GetMax() > MaxGridDimension) { UE_LOG(LogLightmassSolver, Warning, TEXT("Volumetric lightmap grid size is too large which can cause potential crashes or long build time, clamping from (%d, %d, %d) to (%d, %d, %d)"), FullGridSize.X, FullGridSize.Y, FullGridSize.Z, FMath::Min(FullGridSize.X, MaxGridDimension), FMath::Min(FullGridSize.Y, MaxGridDimension), FMath::Min(FullGridSize.Z, MaxGridDimension) ); FullGridSize.X = FMath::Min(FullGridSize.X, MaxGridDimension); FullGridSize.Y = FMath::Min(FullGridSize.Y, MaxGridDimension); FullGridSize.Z = FMath::Min(FullGridSize.Z, MaxGridDimension); } const int32 BrickSizeLog2 = FMath::FloorLog2(OutSettings.BrickSize); const int32 DetailCellsPerTopLevelBrick = 1 << (OutSettings.MaxRefinementLevels * BrickSizeLog2); OutSettings.TopLevelGridSize = FIntVector::DivideAndRoundUp(FullGridSize, DetailCellsPerTopLevelBrick); OutSettings.VolumeSize = FVector3f(OutSettings.TopLevelGridSize) * DetailCellsPerTopLevelBrick * TargetDetailCellSize; } /** Fills out the Scene's settings, read from the engine ini. */ void FLightmassExporter::WriteSceneSettings( Lightmass::FSceneFileHeader& Scene ) { bool bConfigBool = false; //@todo - need a mechanism to automatically catch when a new setting has been added but doesn't get initialized { VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.StaticLighting"), TEXT("bAllowMultiThreadedStaticLighting"), bConfigBool, GLightmassIni)); Scene.GeneralSettings.bAllowMultiThreadedStaticLighting = bConfigBool; Scene.GeneralSettings.NumUnusedLocalCores = NumUnusedLocalCores; Scene.GeneralSettings.NumIndirectLightingBounces = LevelSettings.NumIndirectLightingBounces; Scene.GeneralSettings.NumSkyLightingBounces = LevelSettings.NumSkyLightingBounces; Scene.GeneralSettings.IndirectLightingSmoothness = LevelSettings.IndirectLightingSmoothness; Scene.GeneralSettings.IndirectLightingQuality = LevelSettings.IndirectLightingQuality; if (QualityLevel == Quality_Preview) { Scene.GeneralSettings.IndirectLightingQuality = FMath::Min(Scene.GeneralSettings.IndirectLightingQuality, 1.0f); } VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.StaticLighting"), TEXT("ViewSingleBounceNumber"), Scene.GeneralSettings.ViewSingleBounceNumber, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.StaticLighting"), TEXT("bUseConservativeTexelRasterization"), bConfigBool, GLightmassIni)); Scene.GeneralSettings.bUseConservativeTexelRasterization = bConfigBool; VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.StaticLighting"), TEXT("bAccountForTexelSize"), bConfigBool, GLightmassIni)); Scene.GeneralSettings.bAccountForTexelSize = bConfigBool; VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.StaticLighting"), TEXT("bUseMaxWeight"), bConfigBool, GLightmassIni)); Scene.GeneralSettings.bUseMaxWeight = bConfigBool; VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.StaticLighting"), TEXT("MaxTriangleLightingSamples"), Scene.GeneralSettings.MaxTriangleLightingSamples, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.StaticLighting"), TEXT("MaxTriangleIrradiancePhotonCacheSamples"), Scene.GeneralSettings.MaxTriangleIrradiancePhotonCacheSamples, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.StaticLighting"), TEXT("bUseEmbree"), bConfigBool, GLightmassIni)); Scene.GeneralSettings.bUseEmbree = bConfigBool; VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.StaticLighting"), TEXT("bVerifyEmbree"), bConfigBool, GLightmassIni)); Scene.GeneralSettings.bVerifyEmbree = Scene.GeneralSettings.bUseEmbree && bConfigBool; VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.StaticLighting"), TEXT("bUseEmbreePacketTracing"), Scene.GeneralSettings.bUseEmbreePacketTracing, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.StaticLighting"), TEXT("bUseFastVoxelization"), Scene.GeneralSettings.bUseFastVoxelization, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.StaticLighting"), TEXT("bUseEmbreeInstancing"), Scene.GeneralSettings.bUseEmbreeInstancing, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.StaticLighting"), TEXT("MappingSurfaceCacheDownsampleFactor"), Scene.GeneralSettings.MappingSurfaceCacheDownsampleFactor, GLightmassIni)); int32 CheckQualityLevel; GConfig->GetInt( TEXT("LightingBuildOptions"), TEXT("QualityLevel"), CheckQualityLevel, GEditorPerProjectIni); CheckQualityLevel = FMath::Clamp(CheckQualityLevel, Quality_Preview, Quality_Production); UE_LOG(LogLightmassSolver, Log, TEXT("LIGHTMASS: Writing scene settings: Quality level %d (%d in INI)"), (int32)(QualityLevel), CheckQualityLevel); if (CheckQualityLevel != QualityLevel) { UE_LOG(LogLightmassSolver, Warning, TEXT("LIGHTMASS: Writing scene settings w/ QualityLevel mismatch! %d vs %d (ini setting)"), (int32)QualityLevel, CheckQualityLevel); } switch (QualityLevel) { case Quality_High: case Quality_Production: Scene.GeneralSettings.bUseErrorColoring = false; Scene.GeneralSettings.UnmappedTexelColor = FLinearColor(0.0f, 0.0f, 0.0f); break; default: { bool bUseErrorColoring = false; GConfig->GetBool( TEXT("LightingBuildOptions"), TEXT("UseErrorColoring"), bUseErrorColoring, GEditorPerProjectIni ); Scene.GeneralSettings.bUseErrorColoring = bUseErrorColoring; if (bUseErrorColoring == false) { Scene.GeneralSettings.UnmappedTexelColor = FLinearColor(0.0f, 0.0f, 0.0f); } else { Scene.GeneralSettings.UnmappedTexelColor = FLinearColor(0.7f, 0.7f, 0.0f); } } break; } } { float GlobalLevelScale = 1.0f; VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.StaticLightingSceneConstants"), TEXT("StaticLightingLevelScale"), GlobalLevelScale, GLightmassIni)); Scene.SceneConstants.StaticLightingLevelScale = GlobalLevelScale * LevelSettings.StaticLightingLevelScale; VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.StaticLightingSceneConstants"), TEXT("VisibilityRayOffsetDistance"), Scene.SceneConstants.VisibilityRayOffsetDistance, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.StaticLightingSceneConstants"), TEXT("VisibilityNormalOffsetDistance"), Scene.SceneConstants.VisibilityNormalOffsetDistance, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.StaticLightingSceneConstants"), TEXT("VisibilityNormalOffsetSampleRadiusScale"), Scene.SceneConstants.VisibilityNormalOffsetSampleRadiusScale, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.StaticLightingSceneConstants"), TEXT("VisibilityTangentOffsetSampleRadiusScale"), Scene.SceneConstants.VisibilityTangentOffsetSampleRadiusScale, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.StaticLightingSceneConstants"), TEXT("SmallestTexelRadius"), Scene.SceneConstants.SmallestTexelRadius, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.StaticLightingSceneConstants"), TEXT("LightGridSize"), Scene.SceneConstants.LightGridSize, GLightmassIni)); } { VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.StaticLightingMaterial"), TEXT("bUseDebugMaterial"), bConfigBool, GLightmassIni)); Scene.MaterialSettings.bUseDebugMaterial = bConfigBool; FString ShowMaterialAttributeName; VERIFYLIGHTMASSINI(GConfig->GetString(TEXT("DevOptions.StaticLightingMaterial"), TEXT("ShowMaterialAttribute"), ShowMaterialAttributeName, GLightmassIni)); Scene.MaterialSettings.ViewMaterialAttribute = Lightmass::VMA_None; if (ShowMaterialAttributeName.Contains(TEXT("Emissive")) ) { Scene.MaterialSettings.ViewMaterialAttribute = Lightmass::VMA_Emissive; } else if (ShowMaterialAttributeName.Contains(TEXT("Diffuse")) || LevelSettings.bVisualizeMaterialDiffuse) { Scene.MaterialSettings.ViewMaterialAttribute = Lightmass::VMA_Diffuse; } else if (ShowMaterialAttributeName.Contains(TEXT("Transmission")) ) { Scene.MaterialSettings.ViewMaterialAttribute = Lightmass::VMA_Transmission; } else if (ShowMaterialAttributeName.Contains(TEXT("Normal")) ) { Scene.MaterialSettings.ViewMaterialAttribute = Lightmass::VMA_Normal; } VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.StaticLightingMaterial"), TEXT("EmissiveSampleSize"), Scene.MaterialSettings.EmissiveSize, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.StaticLightingMaterial"), TEXT("DiffuseSampleSize"), Scene.MaterialSettings.DiffuseSize, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.StaticLightingMaterial"), TEXT("TransmissionSampleSize"), Scene.MaterialSettings.TransmissionSize, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.StaticLightingMaterial"), TEXT("NormalSampleSize"), Scene.MaterialSettings.NormalSize, GLightmassIni)); const FString DiffuseStr = GConfig->GetStr(TEXT("DevOptions.StaticLightingMaterial"), TEXT("DebugDiffuse"), GLightmassIni); VERIFYLIGHTMASSINI(FParse::Value(*DiffuseStr, TEXT("R="), Scene.MaterialSettings.DebugDiffuse.R)); VERIFYLIGHTMASSINI(FParse::Value(*DiffuseStr, TEXT("G="), Scene.MaterialSettings.DebugDiffuse.G)); VERIFYLIGHTMASSINI(FParse::Value(*DiffuseStr, TEXT("B="), Scene.MaterialSettings.DebugDiffuse.B)); Scene.MaterialSettings.EnvironmentColor = FLinearColor(LevelSettings.EnvironmentColor) * LevelSettings.EnvironmentIntensity; static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.NormalMapsForStaticLighting")); Scene.MaterialSettings.bUseNormalMapsForLighting = CVar->GetValueOnGameThread() != 0; } { VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.MeshAreaLights"), TEXT("bVisualizeMeshAreaLightPrimitives"), bConfigBool, GLightmassIni)); Scene.MeshAreaLightSettings.bVisualizeMeshAreaLightPrimitives = bConfigBool; VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.MeshAreaLights"), TEXT("EmissiveIntensityThreshold"), Scene.MeshAreaLightSettings.EmissiveIntensityThreshold, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.MeshAreaLights"), TEXT("MeshAreaLightGridSize"), Scene.MeshAreaLightSettings.MeshAreaLightGridSize, GLightmassIni)); float MeshAreaLightSimplifyNormalAngleThreshold; VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.MeshAreaLights"), TEXT("MeshAreaLightSimplifyNormalAngleThreshold"), MeshAreaLightSimplifyNormalAngleThreshold, GLightmassIni)); Scene.MeshAreaLightSettings.MeshAreaLightSimplifyNormalCosAngleThreshold = FMath::Cos(FMath::Clamp(MeshAreaLightSimplifyNormalAngleThreshold, 0.0f, 90.0f) * (float)PI / 180.0f); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.MeshAreaLights"), TEXT("MeshAreaLightSimplifyCornerDistanceThreshold"), Scene.MeshAreaLightSettings.MeshAreaLightSimplifyCornerDistanceThreshold, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.MeshAreaLights"), TEXT("MeshAreaLightSimplifyMeshBoundingRadiusFractionThreshold"), Scene.MeshAreaLightSettings.MeshAreaLightSimplifyMeshBoundingRadiusFractionThreshold, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.MeshAreaLights"), TEXT("MeshAreaLightGeneratedDynamicLightSurfaceOffset"), Scene.MeshAreaLightSettings.MeshAreaLightGeneratedDynamicLightSurfaceOffset, GLightmassIni)); } { Scene.AmbientOcclusionSettings.bUseAmbientOcclusion = LevelSettings.bUseAmbientOcclusion; Scene.AmbientOcclusionSettings.bGenerateAmbientOcclusionMaterialMask = LevelSettings.bGenerateAmbientOcclusionMaterialMask; Scene.AmbientOcclusionSettings.bVisualizeAmbientOcclusion = LevelSettings.bVisualizeAmbientOcclusion; Scene.AmbientOcclusionSettings.DirectIlluminationOcclusionFraction = LevelSettings.DirectIlluminationOcclusionFraction; Scene.AmbientOcclusionSettings.IndirectIlluminationOcclusionFraction = LevelSettings.IndirectIlluminationOcclusionFraction; Scene.AmbientOcclusionSettings.OcclusionExponent = LevelSettings.OcclusionExponent; Scene.AmbientOcclusionSettings.FullyOccludedSamplesFraction = LevelSettings.FullyOccludedSamplesFraction; Scene.AmbientOcclusionSettings.MaxOcclusionDistance = LevelSettings.MaxOcclusionDistance; } { VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.PrecomputedDynamicObjectLighting"), TEXT("bVisualizeVolumeLightSamples"), bConfigBool, GLightmassIni)); Scene.DynamicObjectSettings.bVisualizeVolumeLightSamples = bConfigBool; VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.PrecomputedDynamicObjectLighting"), TEXT("bVisualizeVolumeLightInterpolation"), bConfigBool, GLightmassIni)); Scene.DynamicObjectSettings.bVisualizeVolumeLightInterpolation = bConfigBool; VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PrecomputedDynamicObjectLighting"), TEXT("NumHemisphereSamplesScale"), Scene.DynamicObjectSettings.NumHemisphereSamplesScale, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PrecomputedDynamicObjectLighting"), TEXT("SurfaceLightSampleSpacing"), Scene.DynamicObjectSettings.SurfaceLightSampleSpacing, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PrecomputedDynamicObjectLighting"), TEXT("FirstSurfaceSampleLayerHeight"), Scene.DynamicObjectSettings.FirstSurfaceSampleLayerHeight, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PrecomputedDynamicObjectLighting"), TEXT("SurfaceSampleLayerHeightSpacing"), Scene.DynamicObjectSettings.SurfaceSampleLayerHeightSpacing, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.PrecomputedDynamicObjectLighting"), TEXT("NumSurfaceSampleLayers"), Scene.DynamicObjectSettings.NumSurfaceSampleLayers, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PrecomputedDynamicObjectLighting"), TEXT("DetailVolumeSampleSpacing"), Scene.DynamicObjectSettings.DetailVolumeSampleSpacing, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PrecomputedDynamicObjectLighting"), TEXT("VolumeLightSampleSpacing"), Scene.DynamicObjectSettings.VolumeLightSampleSpacing, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.PrecomputedDynamicObjectLighting"), TEXT("MaxVolumeSamples"), Scene.DynamicObjectSettings.MaxVolumeSamples, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.PrecomputedDynamicObjectLighting"), TEXT("bUseMaxSurfaceSampleNum"), bConfigBool, GLightmassIni)); Scene.DynamicObjectSettings.bUseMaxSurfaceSampleNum = bConfigBool; VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.PrecomputedDynamicObjectLighting"), TEXT("MaxSurfaceLightSamples"), Scene.DynamicObjectSettings.MaxSurfaceLightSamples, GLightmassIni)); Scene.DynamicObjectSettings.SurfaceLightSampleSpacing *= LevelSettings.VolumeLightSamplePlacementScale; Scene.DynamicObjectSettings.VolumeLightSampleSpacing *= LevelSettings.VolumeLightSamplePlacementScale; Scene.DynamicObjectSettings.DetailVolumeSampleSpacing *= LevelSettings.VolumeLightSamplePlacementScale; } { SetVolumetricLightmapSettings(Scene.VolumetricLightmapSettings); } { VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.PrecomputedVisibility"), TEXT("bVisualizePrecomputedVisibility"), bConfigBool, GLightmassIni)); Scene.PrecomputedVisibilitySettings.bVisualizePrecomputedVisibility = bConfigBool; VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.PrecomputedVisibility"), TEXT("bPlaceCellsOnOpaqueOnly"), bConfigBool, GLightmassIni)); Scene.PrecomputedVisibilitySettings.bPlaceCellsOnOpaqueOnly = bConfigBool; Scene.PrecomputedVisibilitySettings.bPlaceCellsOnlyAlongCameraTracks = World->GetWorldSettings()->bPlaceCellsOnlyAlongCameraTracks; Scene.PrecomputedVisibilitySettings.CellSize = World->GetWorldSettings()->VisibilityCellSize; VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.PrecomputedVisibility"), TEXT("NumCellDistributionBuckets"), Scene.PrecomputedVisibilitySettings.NumCellDistributionBuckets, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PrecomputedVisibility"), TEXT("PlayAreaHeight"), Scene.PrecomputedVisibilitySettings.PlayAreaHeight, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PrecomputedVisibility"), TEXT("MeshBoundsScale"), Scene.PrecomputedVisibilitySettings.MeshBoundsScale, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.PrecomputedVisibility"), TEXT("MinMeshSamples"), Scene.PrecomputedVisibilitySettings.MinMeshSamples, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.PrecomputedVisibility"), TEXT("MaxMeshSamples"), Scene.PrecomputedVisibilitySettings.MaxMeshSamples, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.PrecomputedVisibility"), TEXT("NumCellSamples"), Scene.PrecomputedVisibilitySettings.NumCellSamples, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.PrecomputedVisibility"), TEXT("NumImportanceSamples"), Scene.PrecomputedVisibilitySettings.NumImportanceSamples, GLightmassIni)); } if (World->GetWorldSettings()->VisibilityAggressiveness != VIS_LeastAggressive) { const TCHAR* AggressivenessSectionNames[VIS_Max] = { TEXT(""), TEXT("DevOptions.PrecomputedVisibilityModeratelyAggressive"), TEXT("DevOptions.PrecomputedVisibilityMostAggressive")}; const TCHAR* ActiveSection = AggressivenessSectionNames[FMath::Clamp((int32)World->GetWorldSettings()->VisibilityAggressiveness, 0, VIS_Max - 1)]; VERIFYLIGHTMASSINI(GConfig->GetFloat(ActiveSection, TEXT("MeshBoundsScale"), Scene.PrecomputedVisibilitySettings.MeshBoundsScale, GLightmassIni)); } { VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.VolumeDistanceField"), TEXT("VoxelSize"), Scene.VolumeDistanceFieldSettings.VoxelSize, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.VolumeDistanceField"), TEXT("VolumeMaxDistance"), Scene.VolumeDistanceFieldSettings.VolumeMaxDistance, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.VolumeDistanceField"), TEXT("NumVoxelDistanceSamples"), Scene.VolumeDistanceFieldSettings.NumVoxelDistanceSamples, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.VolumeDistanceField"), TEXT("MaxVoxels"), Scene.VolumeDistanceFieldSettings.MaxVoxels, GLightmassIni)); } { VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.StaticShadows"), TEXT("bUseZeroAreaLightmapSpaceFilteredLights"), bConfigBool, GLightmassIni)); Scene.ShadowSettings.bUseZeroAreaLightmapSpaceFilteredLights = bConfigBool; VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.StaticShadows"), TEXT("NumShadowRays"), Scene.ShadowSettings.NumShadowRays, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.StaticShadows"), TEXT("NumPenumbraShadowRays"), Scene.ShadowSettings.NumPenumbraShadowRays, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.StaticShadows"), TEXT("NumBounceShadowRays"), Scene.ShadowSettings.NumBounceShadowRays, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.StaticShadows"), TEXT("bFilterShadowFactor"), bConfigBool, GLightmassIni)); Scene.ShadowSettings.bFilterShadowFactor = bConfigBool; VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.StaticShadows"), TEXT("ShadowFactorGradientTolerance"), Scene.ShadowSettings.ShadowFactorGradientTolerance, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.StaticShadows"), TEXT("bAllowSignedDistanceFieldShadows"), bConfigBool, GLightmassIni)); Scene.ShadowSettings.bAllowSignedDistanceFieldShadows = bConfigBool; VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.StaticShadows"), TEXT("MaxTransitionDistanceWorldSpace"), Scene.ShadowSettings.MaxTransitionDistanceWorldSpace, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.StaticShadows"), TEXT("ApproximateHighResTexelsPerMaxTransitionDistance"), Scene.ShadowSettings.ApproximateHighResTexelsPerMaxTransitionDistance, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.StaticShadows"), TEXT("MinDistanceFieldUpsampleFactor"), Scene.ShadowSettings.MinDistanceFieldUpsampleFactor, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.StaticShadows"), TEXT("StaticShadowDepthMapTransitionSampleDistanceX"), Scene.ShadowSettings.StaticShadowDepthMapTransitionSampleDistanceX, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.StaticShadows"), TEXT("StaticShadowDepthMapTransitionSampleDistanceY"), Scene.ShadowSettings.StaticShadowDepthMapTransitionSampleDistanceY, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.StaticShadows"), TEXT("StaticShadowDepthMapSuperSampleFactor"), Scene.ShadowSettings.StaticShadowDepthMapSuperSampleFactor, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.StaticShadows"), TEXT("StaticShadowDepthMapMaxSamples"), Scene.ShadowSettings.StaticShadowDepthMapMaxSamples, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.StaticShadows"), TEXT("MinUnoccludedFraction"), Scene.ShadowSettings.MinUnoccludedFraction, GLightmassIni)); } { VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.ImportanceTracing"), TEXT("bUseStratifiedSampling"), bConfigBool, GLightmassIni)); Scene.ImportanceTracingSettings.bUseStratifiedSampling = bConfigBool; VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.ImportanceTracing"), TEXT("NumHemisphereSamples"), Scene.ImportanceTracingSettings.NumHemisphereSamples, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.ImportanceTracing"), TEXT("NumAdaptiveRefinementLevels"), Scene.ImportanceTracingSettings.NumAdaptiveRefinementLevels, GLightmassIni)); float MaxHemisphereAngleDegrees; VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.ImportanceTracing"), TEXT("MaxHemisphereRayAngle"), MaxHemisphereAngleDegrees, GLightmassIni)); Scene.ImportanceTracingSettings.MaxHemisphereRayAngle = MaxHemisphereAngleDegrees * (float)PI / 180.0f; VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.ImportanceTracing"), TEXT("AdaptiveBrightnessThreshold"), Scene.ImportanceTracingSettings.AdaptiveBrightnessThreshold, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.ImportanceTracing"), TEXT("AdaptiveFirstBouncePhotonConeAngle"), Scene.ImportanceTracingSettings.AdaptiveFirstBouncePhotonConeAngle, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.ImportanceTracing"), TEXT("AdaptiveSkyVarianceThreshold"), Scene.ImportanceTracingSettings.AdaptiveSkyVarianceThreshold, GLightmassIni)); float AdaptiveFirstBouncePhotonConeAngle; VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.ImportanceTracing"), TEXT("AdaptiveFirstBouncePhotonConeAngle"), AdaptiveFirstBouncePhotonConeAngle, GLightmassIni)); Scene.ImportanceTracingSettings.AdaptiveFirstBouncePhotonConeAngle = FMath::Clamp(AdaptiveFirstBouncePhotonConeAngle, 0.0f, 90.0f) * (float)PI / 180.0f; VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.ImportanceTracing"), TEXT("bUseRadiositySolverForSkylightMultibounce"), Scene.ImportanceTracingSettings.bUseRadiositySolverForSkylightMultibounce, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.ImportanceTracing"), TEXT("bCacheFinalGatherHitPointsForRadiosity"), Scene.ImportanceTracingSettings.bCacheFinalGatherHitPointsForRadiosity, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.ImportanceTracing"), TEXT("bUseRadiositySolverForLightMultibounce"), Scene.ImportanceTracingSettings.bUseRadiositySolverForLightMultibounce, GLightmassIni)); } { VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.PhotonMapping"), TEXT("bUsePhotonMapping"), bConfigBool, GLightmassIni)); Scene.PhotonMappingSettings.bUsePhotonMapping = bConfigBool; VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.PhotonMapping"), TEXT("bUseFinalGathering"), bConfigBool, GLightmassIni)); Scene.PhotonMappingSettings.bUseFinalGathering = bConfigBool; VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.PhotonMapping"), TEXT("bUsePhotonDirectLightingInFinalGather"), bConfigBool, GLightmassIni)); Scene.PhotonMappingSettings.bUsePhotonDirectLightingInFinalGather = bConfigBool; VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.PhotonMapping"), TEXT("bVisualizeCachedApproximateDirectLighting"), bConfigBool, GLightmassIni)); Scene.PhotonMappingSettings.bVisualizeCachedApproximateDirectLighting = bConfigBool; VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.PhotonMapping"), TEXT("bUseIrradiancePhotons"), bConfigBool, GLightmassIni)); Scene.PhotonMappingSettings.bUseIrradiancePhotons = bConfigBool; VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.PhotonMapping"), TEXT("bCacheIrradiancePhotonsOnSurfaces"), bConfigBool, GLightmassIni)); Scene.PhotonMappingSettings.bCacheIrradiancePhotonsOnSurfaces = bConfigBool; VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.PhotonMapping"), TEXT("bVisualizePhotonPaths"), bConfigBool, GLightmassIni)); Scene.PhotonMappingSettings.bVisualizePhotonPaths = bConfigBool; VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.PhotonMapping"), TEXT("bVisualizePhotonGathers"), bConfigBool, GLightmassIni)); Scene.PhotonMappingSettings.bVisualizePhotonGathers = bConfigBool; VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.PhotonMapping"), TEXT("bVisualizePhotonImportanceSamples"), bConfigBool, GLightmassIni)); Scene.PhotonMappingSettings.bVisualizePhotonImportanceSamples = bConfigBool; VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.PhotonMapping"), TEXT("bVisualizeIrradiancePhotonCalculation"), bConfigBool, GLightmassIni)); Scene.PhotonMappingSettings.bVisualizeIrradiancePhotonCalculation = bConfigBool; VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.PhotonMapping"), TEXT("bEmitPhotonsOutsideImportanceVolume"), bConfigBool, GLightmassIni)); Scene.PhotonMappingSettings.bEmitPhotonsOutsideImportanceVolume = bConfigBool; VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PhotonMapping"), TEXT("ConeFilterConstant"), Scene.PhotonMappingSettings.ConeFilterConstant, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.PhotonMapping"), TEXT("NumIrradianceCalculationPhotons"), Scene.PhotonMappingSettings.NumIrradianceCalculationPhotons, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PhotonMapping"), TEXT("FinalGatherImportanceSampleFraction"), Scene.PhotonMappingSettings.FinalGatherImportanceSampleFraction, GLightmassIni)); float FinalGatherImportanceSampleConeAngle; VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PhotonMapping"), TEXT("FinalGatherImportanceSampleConeAngle"), FinalGatherImportanceSampleConeAngle, GLightmassIni)); Scene.PhotonMappingSettings.FinalGatherImportanceSampleCosConeAngle = FMath::Cos(FMath::Clamp(FinalGatherImportanceSampleConeAngle, 0.0f, 90.0f) * (float)PI / 180.0f); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PhotonMapping"), TEXT("IndirectPhotonEmitDiskRadius"), Scene.PhotonMappingSettings.IndirectPhotonEmitDiskRadius, GLightmassIni)); float IndirectPhotonEmitConeAngleDegrees; VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PhotonMapping"), TEXT("IndirectPhotonEmitConeAngle"), IndirectPhotonEmitConeAngleDegrees, GLightmassIni)); Scene.PhotonMappingSettings.IndirectPhotonEmitConeAngle = FMath::Clamp(IndirectPhotonEmitConeAngleDegrees, 0.0f, 90.0f) * (float)PI / 180.0f; VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PhotonMapping"), TEXT("MaxImportancePhotonSearchDistance"), Scene.PhotonMappingSettings.MaxImportancePhotonSearchDistance, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PhotonMapping"), TEXT("MinImportancePhotonSearchDistance"), Scene.PhotonMappingSettings.MinImportancePhotonSearchDistance, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.PhotonMapping"), TEXT("NumImportanceSearchPhotons"), Scene.PhotonMappingSettings.NumImportanceSearchPhotons, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PhotonMapping"), TEXT("OutsideImportanceVolumeDensityScale"), Scene.PhotonMappingSettings.OutsideImportanceVolumeDensityScale, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PhotonMapping"), TEXT("DirectPhotonDensity"), Scene.PhotonMappingSettings.DirectPhotonDensity, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PhotonMapping"), TEXT("DirectIrradiancePhotonDensity"), Scene.PhotonMappingSettings.DirectIrradiancePhotonDensity, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PhotonMapping"), TEXT("DirectPhotonSearchDistance"), Scene.PhotonMappingSettings.DirectPhotonSearchDistance, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PhotonMapping"), TEXT("IndirectPhotonPathDensity"), Scene.PhotonMappingSettings.IndirectPhotonPathDensity, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PhotonMapping"), TEXT("IndirectPhotonDensity"), Scene.PhotonMappingSettings.IndirectPhotonDensity, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PhotonMapping"), TEXT("IndirectIrradiancePhotonDensity"), Scene.PhotonMappingSettings.IndirectIrradiancePhotonDensity, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PhotonMapping"), TEXT("IndirectPhotonSearchDistance"), Scene.PhotonMappingSettings.IndirectPhotonSearchDistance, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PhotonMapping"), TEXT("PhotonSearchAngleThreshold"), Scene.PhotonMappingSettings.PhotonSearchAngleThreshold, GLightmassIni)); float IrradiancePhotonSearchConeAngle; VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PhotonMapping"), TEXT("IrradiancePhotonSearchConeAngle"), IrradiancePhotonSearchConeAngle, GLightmassIni)); Scene.PhotonMappingSettings.MinCosIrradiancePhotonSearchCone = FMath::Cos((90.0f - FMath::Clamp(IrradiancePhotonSearchConeAngle, 1.0f, 90.0f)) * (float)PI / 180.0f); VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.PhotonMapping"), TEXT("bUsePhotonSegmentsForVolumeLighting"), Scene.PhotonMappingSettings.bUsePhotonSegmentsForVolumeLighting, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PhotonMapping"), TEXT("PhotonSegmentMaxLength"), Scene.PhotonMappingSettings.PhotonSegmentMaxLength, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PhotonMapping"), TEXT("GeneratePhotonSegmentChance"), Scene.PhotonMappingSettings.GeneratePhotonSegmentChance, GLightmassIni)); } { VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.IrradianceCache"), TEXT("bAllowIrradianceCaching"), bConfigBool, GLightmassIni)); Scene.IrradianceCachingSettings.bAllowIrradianceCaching = bConfigBool; VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.IrradianceCache"), TEXT("bUseIrradianceGradients"), bConfigBool, GLightmassIni)); Scene.IrradianceCachingSettings.bUseIrradianceGradients = bConfigBool; VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.IrradianceCache"), TEXT("bShowGradientsOnly"), bConfigBool, GLightmassIni)); Scene.IrradianceCachingSettings.bShowGradientsOnly = bConfigBool; VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.IrradianceCache"), TEXT("bVisualizeIrradianceSamples"), bConfigBool, GLightmassIni)); Scene.IrradianceCachingSettings.bVisualizeIrradianceSamples = bConfigBool; VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.IrradianceCache"), TEXT("RecordRadiusScale"), Scene.IrradianceCachingSettings.RecordRadiusScale, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.IrradianceCache"), TEXT("InterpolationMaxAngle"), Scene.IrradianceCachingSettings.InterpolationMaxAngle, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.IrradianceCache"), TEXT("PointBehindRecordMaxAngle"), Scene.IrradianceCachingSettings.PointBehindRecordMaxAngle, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.IrradianceCache"), TEXT("DistanceSmoothFactor"), Scene.IrradianceCachingSettings.DistanceSmoothFactor, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.IrradianceCache"), TEXT("AngleSmoothFactor"), Scene.IrradianceCachingSettings.AngleSmoothFactor, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.IrradianceCache"), TEXT("SkyOcclusionSmoothnessReduction"), Scene.IrradianceCachingSettings.SkyOcclusionSmoothnessReduction, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.IrradianceCache"), TEXT("MaxRecordRadius"), Scene.IrradianceCachingSettings.MaxRecordRadius, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.IrradianceCache"), TEXT("CacheTaskSize"), Scene.IrradianceCachingSettings.CacheTaskSize, GLightmassIni)); VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.IrradianceCache"), TEXT("InterpolateTaskSize"), Scene.IrradianceCachingSettings.InterpolateTaskSize, GLightmassIni)); } // Modify settings based on the quality level required // Preview is assumed to have a scale of 1 for all settings and therefore is not in the ini if (QualityLevel != Quality_Preview) { const TCHAR* QualitySectionNames[Quality_MAX] = { TEXT(""), TEXT("DevOptions.StaticLightingMediumQuality"), TEXT("DevOptions.StaticLightingHighQuality"), TEXT("DevOptions.StaticLightingProductionQuality")}; float NumShadowRaysScale; VERIFYLIGHTMASSINI(GConfig->GetFloat(QualitySectionNames[QualityLevel], TEXT("NumShadowRaysScale"), NumShadowRaysScale, GLightmassIni)); Scene.ShadowSettings.NumShadowRays = FMath::TruncToInt(Scene.ShadowSettings.NumShadowRays * NumShadowRaysScale); float NumPenumbraShadowRaysScale; VERIFYLIGHTMASSINI(GConfig->GetFloat(QualitySectionNames[QualityLevel], TEXT("NumPenumbraShadowRaysScale"), NumPenumbraShadowRaysScale, GLightmassIni)); Scene.ShadowSettings.NumPenumbraShadowRays = FMath::TruncToInt(Scene.ShadowSettings.NumPenumbraShadowRays * NumPenumbraShadowRaysScale); float ApproximateHighResTexelsPerMaxTransitionDistanceScale; VERIFYLIGHTMASSINI(GConfig->GetFloat(QualitySectionNames[QualityLevel], TEXT("ApproximateHighResTexelsPerMaxTransitionDistanceScale"), ApproximateHighResTexelsPerMaxTransitionDistanceScale, GLightmassIni)); Scene.ShadowSettings.ApproximateHighResTexelsPerMaxTransitionDistance = FMath::TruncToInt(Scene.ShadowSettings.ApproximateHighResTexelsPerMaxTransitionDistance * ApproximateHighResTexelsPerMaxTransitionDistanceScale); VERIFYLIGHTMASSINI(GConfig->GetInt(QualitySectionNames[QualityLevel], TEXT("MinDistanceFieldUpsampleFactor"), Scene.ShadowSettings.MinDistanceFieldUpsampleFactor, GLightmassIni)); float NumHemisphereSamplesScale; VERIFYLIGHTMASSINI(GConfig->GetFloat(QualitySectionNames[QualityLevel], TEXT("NumHemisphereSamplesScale"), NumHemisphereSamplesScale, GLightmassIni)); Scene.ImportanceTracingSettings.NumHemisphereSamples = FMath::TruncToInt(Scene.ImportanceTracingSettings.NumHemisphereSamples * NumHemisphereSamplesScale); float NumImportanceSearchPhotonsScale; VERIFYLIGHTMASSINI(GConfig->GetFloat(QualitySectionNames[QualityLevel], TEXT("NumImportanceSearchPhotonsScale"), NumImportanceSearchPhotonsScale, GLightmassIni)); Scene.PhotonMappingSettings.NumImportanceSearchPhotons = FMath::TruncToInt(Scene.PhotonMappingSettings.NumImportanceSearchPhotons * NumImportanceSearchPhotonsScale); float NumDirectPhotonsScale; VERIFYLIGHTMASSINI(GConfig->GetFloat(QualitySectionNames[QualityLevel], TEXT("NumDirectPhotonsScale"), NumDirectPhotonsScale, GLightmassIni)); Scene.PhotonMappingSettings.DirectPhotonDensity = Scene.PhotonMappingSettings.DirectPhotonDensity * NumDirectPhotonsScale; Scene.PhotonMappingSettings.DirectIrradiancePhotonDensity = Scene.PhotonMappingSettings.DirectIrradiancePhotonDensity * NumDirectPhotonsScale; float DirectPhotonSearchDistanceScale; VERIFYLIGHTMASSINI(GConfig->GetFloat(QualitySectionNames[QualityLevel], TEXT("DirectPhotonSearchDistanceScale"), DirectPhotonSearchDistanceScale, GLightmassIni)); Scene.PhotonMappingSettings.DirectPhotonSearchDistance = Scene.PhotonMappingSettings.DirectPhotonSearchDistance * DirectPhotonSearchDistanceScale; float NumIndirectPhotonPathsScale; VERIFYLIGHTMASSINI(GConfig->GetFloat(QualitySectionNames[QualityLevel], TEXT("NumIndirectPhotonPathsScale"), NumIndirectPhotonPathsScale, GLightmassIni)); Scene.PhotonMappingSettings.IndirectPhotonPathDensity = Scene.PhotonMappingSettings.IndirectPhotonPathDensity * NumIndirectPhotonPathsScale; float NumIndirectPhotonsScale; VERIFYLIGHTMASSINI(GConfig->GetFloat(QualitySectionNames[QualityLevel], TEXT("NumIndirectPhotonsScale"), NumIndirectPhotonsScale, GLightmassIni)); Scene.PhotonMappingSettings.IndirectPhotonDensity = Scene.PhotonMappingSettings.IndirectPhotonDensity * NumIndirectPhotonsScale; float NumIndirectIrradiancePhotonsScale; VERIFYLIGHTMASSINI(GConfig->GetFloat(QualitySectionNames[QualityLevel], TEXT("NumIndirectIrradiancePhotonsScale"), NumIndirectIrradiancePhotonsScale, GLightmassIni)); Scene.PhotonMappingSettings.IndirectIrradiancePhotonDensity = Scene.PhotonMappingSettings.IndirectIrradiancePhotonDensity * NumIndirectIrradiancePhotonsScale; float RecordRadiusScaleScale; VERIFYLIGHTMASSINI(GConfig->GetFloat(QualitySectionNames[QualityLevel], TEXT("RecordRadiusScaleScale"), RecordRadiusScaleScale, GLightmassIni)); Scene.IrradianceCachingSettings.RecordRadiusScale = Scene.IrradianceCachingSettings.RecordRadiusScale * RecordRadiusScaleScale; float InterpolationMaxAngleScale; VERIFYLIGHTMASSINI(GConfig->GetFloat(QualitySectionNames[QualityLevel], TEXT("InterpolationMaxAngleScale"), InterpolationMaxAngleScale, GLightmassIni)); Scene.IrradianceCachingSettings.InterpolationMaxAngle = Scene.IrradianceCachingSettings.InterpolationMaxAngle * InterpolationMaxAngleScale; float IrradianceCacheSmoothFactor; VERIFYLIGHTMASSINI(GConfig->GetFloat(QualitySectionNames[QualityLevel], TEXT("IrradianceCacheSmoothFactor"), IrradianceCacheSmoothFactor, GLightmassIni)); Scene.IrradianceCachingSettings.DistanceSmoothFactor *= IrradianceCacheSmoothFactor; Scene.IrradianceCachingSettings.AngleSmoothFactor *= IrradianceCacheSmoothFactor; VERIFYLIGHTMASSINI(GConfig->GetInt(QualitySectionNames[QualityLevel], TEXT("NumAdaptiveRefinementLevels"), Scene.ImportanceTracingSettings.NumAdaptiveRefinementLevels, GLightmassIni)); float AdaptiveBrightnessThresholdScale; VERIFYLIGHTMASSINI(GConfig->GetFloat(QualitySectionNames[QualityLevel], TEXT("AdaptiveBrightnessThresholdScale"), AdaptiveBrightnessThresholdScale, GLightmassIni)); Scene.ImportanceTracingSettings.AdaptiveBrightnessThreshold = Scene.ImportanceTracingSettings.AdaptiveBrightnessThreshold * AdaptiveBrightnessThresholdScale; float AdaptiveFirstBouncePhotonConeAngleScale; VERIFYLIGHTMASSINI(GConfig->GetFloat(QualitySectionNames[QualityLevel], TEXT("AdaptiveFirstBouncePhotonConeAngleScale"), AdaptiveFirstBouncePhotonConeAngleScale, GLightmassIni)); Scene.ImportanceTracingSettings.AdaptiveFirstBouncePhotonConeAngle = Scene.ImportanceTracingSettings.AdaptiveFirstBouncePhotonConeAngle * AdaptiveFirstBouncePhotonConeAngleScale; float AdaptiveSkyVarianceThresholdScale; VERIFYLIGHTMASSINI(GConfig->GetFloat(QualitySectionNames[QualityLevel], TEXT("AdaptiveSkyVarianceThresholdScale"), AdaptiveSkyVarianceThresholdScale, GLightmassIni)); Scene.ImportanceTracingSettings.AdaptiveSkyVarianceThreshold = Scene.ImportanceTracingSettings.AdaptiveSkyVarianceThreshold * AdaptiveSkyVarianceThresholdScale; } } /** Fills InputData with debug information */ void FLightmassExporter::WriteDebugInput( Lightmass::FDebugLightingInputData& InputData, FGuid& DebugMappingGuid ) { InputData.bRelaySolverStats = UE_LOG_ACTIVE(LogLightmassSolver, Log); if (IsTexelDebuggingEnabled()) { FindDebugMapping(DebugMappingGuid); } InputData.MappingGuid = DebugMappingGuid; InputData.NodeIndex = GCurrentSelectedLightmapSample.NodeIndex; InputData.Position = FVector4f(FVector3f(GCurrentSelectedLightmapSample.Position), 0); InputData.LocalX = GCurrentSelectedLightmapSample.LocalX; InputData.LocalY = GCurrentSelectedLightmapSample.LocalY; InputData.MappingSizeX = GCurrentSelectedLightmapSample.MappingSizeX; InputData.MappingSizeY = GCurrentSelectedLightmapSample.MappingSizeY; FVector4 ViewPosition(0, 0, 0, 0); for (FLevelEditorViewportClient* LevelVC : GEditor->GetLevelViewportClients()) { if (LevelVC->IsPerspective()) { ViewPosition = LevelVC->GetViewLocation(); } } InputData.CameraPosition = (FVector4f)ViewPosition; int32 DebugVisibilityId = INDEX_NONE; bool bVisualizePrecomputedVisibility = false; VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.PrecomputedVisibility"), TEXT("bVisualizePrecomputedVisibility"), bVisualizePrecomputedVisibility, GLightmassIni)); if (bVisualizePrecomputedVisibility) { for (FSelectedActorIterator It(World); It; ++It) { for (UActorComponent* Comp : It->GetComponents()) { if (UPrimitiveComponent* Component = Cast(Comp)) { if (DebugVisibilityId == INDEX_NONE) { DebugVisibilityId = Component->VisibilityId; } else if (DebugVisibilityId != Component->VisibilityId) { UE_LOG(LogLightmassSolver, Warning, TEXT("Not debugging visibility for component %s with vis id %u, as it was not the first component on the selected actor."), *Component->GetPathName(), Component->VisibilityId); } } } } for (int32 LevelIndex= 0; LevelIndex < World->GetNumLevels(); LevelIndex++) { ULevel* Level = World->GetLevel(LevelIndex); for (int32 SurfIdx = 0; SurfIdx < Level->Model->Surfs.Num(); SurfIdx++) { const FBspSurf& Surf = Level->Model->Surfs[SurfIdx]; if ((Surf.PolyFlags & PF_Selected) != 0) { for (int32 NodeIdx = 0; NodeIdx < Level->Model->Nodes.Num(); NodeIdx++) { const FBspNode& Node = Level->Model->Nodes[NodeIdx]; if (Node.iSurf == SurfIdx) { UModelComponent* SomeModelComponent = Level->ModelComponents[Node.ComponentIndex]; if (DebugVisibilityId == INDEX_NONE) { DebugVisibilityId = SomeModelComponent->VisibilityId; } else if (DebugVisibilityId != SomeModelComponent->VisibilityId) { UE_LOG(LogLightmassSolver, Warning, TEXT("Not debugging visibility for model component %s with vis id %u!"), *SomeModelComponent->GetPathName(), SomeModelComponent->VisibilityId); } } } } } } } InputData.DebugVisibilityId = DebugVisibilityId; } void FLightmassExporter::UpdateExportProgress() { CurrentProgress++; // Update rarely to reduce time spent redrawing the status, which can be significant const int32 ProgressUpdateFrequency = FMath::Max(TotalProgress / 20,1); if (CurrentProgress % ProgressUpdateFrequency == 0) { GWarn->UpdateProgress(CurrentProgress, TotalProgress); } } void FLightmassExporter::AddLight(ULightComponentBase* Light) { UDirectionalLightComponent* DirectionalLight = Cast(Light); UPointLightComponent* PointLight = Cast(Light); USpotLightComponent* SpotLight = Cast(Light); URectLightComponent* RectLight = Cast(Light); USkyLightComponent* SkyLight = Cast(Light); if( DirectionalLight ) { DirectionalLights.AddUnique(DirectionalLight); } else if( SpotLight ) { SpotLights.AddUnique(SpotLight); } else if( PointLight ) { PointLights.AddUnique(PointLight); } else if( RectLight ) { RectLights.AddUnique(RectLight); } else if( SkyLight ) { SkyLights.AddUnique(SkyLight); } } /*----------------------------------------------------------------------------- FLightmassProcessor -----------------------------------------------------------------------------*/ /** * Constructor * * @param bInDumpBinaryResults true if it should dump out raw binary lighting data to disk */ FLightmassProcessor::FLightmassProcessor(const FStaticLightingSystem& InSystem, bool bInDumpBinaryResults, bool bInOnlyBuildVisibility) : Exporter(NULL) , Importer(NULL) , System(InSystem) , Swarm( NSwarm::FSwarmInterface::Get() ) , bProcessingSuccessful(false) , bProcessingFailed(false) , bQuitReceived(false) , NumCompletedTasks(0) , bRunningLightmass(false) , bOnlyBuildVisibility( bInOnlyBuildVisibility ) , bDumpBinaryResults( bInDumpBinaryResults ) , bImportCompletedMappingsImmediately(false) , MappingToProcessIndex(0) { // Since these can be set by the commandline, we need to update them here... GLightmassDebugOptions.bDebugMode = GLightmassDebugMode; GLightmassDebugOptions.bStatsEnabled = GLightmassStatsMode; NSwarm::TLogFlags LogFlags = NSwarm::SWARM_LOG_NONE; if (GLightmassDebugOptions.bStatsEnabled) { LogFlags = NSwarm::TLogFlags(LogFlags | NSwarm::SWARM_LOG_TIMINGS); } FString OptionsFolder = FPaths::Combine(*FPaths::GameAgnosticSavedDir(), TEXT("Swarm")); OptionsFolder = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*OptionsFolder); int32 ConnectionHandle = Swarm.OpenConnection( SwarmCallback, this, LogFlags, *OptionsFolder ); bSwarmConnectionIsValid = (ConnectionHandle >= 0); Exporter = new FLightmassExporter( System.GetLightingContext() ); check(Exporter); Exporter->bSwarmConnectionIsValid = bSwarmConnectionIsValid; DeferredMappingsDirectory = InSystem.Options.MappingsDirectory; Messages.Add( TEXT("UseErrorColoringButton_Tooltip"), LOCTEXT("UseErrorColoringButton_Tooltip", "Display objects with lighting errors in identifying colors rather than black (Lightmass only).") ); Messages.Add( TEXT("LightmassError_SupportFP"), LOCTEXT("LightmassError_SupportFP", "Lightmass requires a graphics card with support for floating point rendertargets. Aborting!") ); Messages.Add( TEXT("LightmassError_MissingImportanceVolume"), LOCTEXT("LightmassError_MissingImportanceVolume", "No importance volume found - lighting build will take a long time and have poor quality.") ); Messages.Add( TEXT("LightmassError_FailedToAllocateShadowmapChannel"), LOCTEXT("LightmassError_FailedToAllocateShadowmapChannel", "Severe performance loss: Failed to allocate shadowmap channel for stationary light due to overlap - light will fall back to dynamic shadows!") ); Messages.Add( TEXT("LightmassError_MissingPrecomputedVisibilityVolume"), LOCTEXT("LightmassError_MissingPrecomputedVisibilityVolume", "Level has bPrecomputeVisibility enabled but no Precomputed Visibility Volumes, precomputed visibility will not be effective.") ); Messages.Add( TEXT("LightmassError_BuildSelected"), LOCTEXT("LightmassError_BuildSelected", "Building selected actors only, lightmap memory and quality will be sub-optimal until the next full rebuild.") ); Messages.Add( TEXT("LightmassError_BuildSelectedNothingSelected"), LOCTEXT("LightmassError_BuildSelectedNothingSelected", "Building selected actors and BSP only, but no actors or BSP selected!") ); Messages.Add( TEXT("LightmassError_ObjectWrappedUVs"), LOCTEXT("LightmassError_ObjectWrappedUVs", "Object has wrapping UVs.") ); Messages.Add( TEXT("LightmassError_ObjectOverlappedUVs"), LOCTEXT("LightmassError_ObjectOverlappedUVs", "Object has overlapping UVs.") ); Messages.Add( TEXT("LightmassError_EmissiveMeshHighPolyCount"), LOCTEXT("LightmassError_EmissiveMeshHighPolyCount", "Object has a large number of polygons (more than 3000) and will result in a long lighting build.") ); Messages.Add( TEXT("LightmassError_EmissiveMeshExtremelyHighPolyCount"), LOCTEXT("LightmassError_EmissiveMeshExtremelyHighPolyCount", "Object did not create emissive lights due to excessive polycount (more than 5000).") ); Messages.Add( TEXT("LightmassError_BadLightMapCoordinateIndex"), LOCTEXT("LightmassError_BadLightMapCoordinateIndex", "StaticMesh has invalid LightMapCoordinateIndex.") ); Messages.Add( TEXT("LightmassError_ObjectMultipleDominantLights"), LOCTEXT("LightmassError_ObjectMultipleDominantLights", "Object has multiple dominant lights.") ); } FLightmassProcessor::~FLightmassProcessor() { // Note: the connection must be closed before deleting anything that SwarmCallback accesses Swarm.CloseConnection(); delete Exporter; delete Importer; for ( TMap::TIterator It(ImportedMappings); It; ++It ) { FMappingImportHelper* ImportData = It.Value(); delete ImportData; } ImportedMappings.Empty(); delete DeferredMappings; FLandscapeStaticLightingMesh::LandscapeUpscaleHeightDataCache.Empty(); } /** Retrieve an exporter for the given channel name */ FLightmassExporter* FLightmassProcessor::GetLightmassExporter() { return Exporter; } FString FLightmassProcessor::GetMappingFileExtension(const FStaticLightingMapping* InMapping) { // Determine the input file name FString FileExtension = TEXT(""); if (InMapping) { if (InMapping->IsTextureMapping() == true) { FileExtension = Lightmass::LM_TEXTUREMAPPING_EXTENSION; } } return FileExtension; } int32 FLightmassProcessor_GetMappingFileVersion(const FStaticLightingMapping* InMapping) { // Determine the input file name int32 ReturnVersion = 0; if (InMapping) { if (InMapping->IsTextureMapping() == true) { ReturnVersion = Lightmass::LM_TEXTUREMAPPING_VERSION; } } return ReturnVersion; } bool FLightmassProcessor::OpenJob() { // Start the Job int32 ErrorCode = Swarm.OpenJob( Exporter->SceneGuid ); if( ErrorCode < 0 ) { UE_LOG(LogLightmassSolver, Log, TEXT("Error, OpenJob failed with error code %d"), ErrorCode ); return false; } return true; } bool FLightmassProcessor::CloseJob() { // All done, end the Job int32 ErrorCode = Swarm.CloseJob(); if( ErrorCode < 0 ) { UE_LOG(LogLightmassSolver, Log, TEXT("Error, CloseJob failed with error code %d"), ErrorCode ); return false; } return true; } void FLightmassProcessor::InitiateExport() { FLightmassStatistics::FScopedGather ExportStatScope(Statistics.ExportTime); // If the Job started successfully, export the scene GWarn->StatusUpdate( 0, 100, LOCTEXT("BeginExportingTheSceneTask", "Exporting the scene...") ); double StartTime = FPlatformTime::Seconds(); int32 NumCellDistributionBuckets; VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.PrecomputedVisibility"), TEXT("NumCellDistributionBuckets"), NumCellDistributionBuckets, GLightmassIni)); if (System.GetWorld()->GetWorldSettings()->bPrecomputeVisibility) { for (int32 DistributionBucketIndex = 0; DistributionBucketIndex < NumCellDistributionBuckets; DistributionBucketIndex++) { Exporter->VisibilityBucketGuids.Add(FGuid::NewGuid()); } } if (System.GetWorld()->GetWorldSettings()->LightmassSettings.VolumeLightingMethod == VLM_VolumetricLightmap && !bOnlyBuildVisibility && !System.Options.bApplyDeferedActorMappingPass ) { Lightmass::FVolumetricLightmapSettings VolumetricLightmapSettings; GetLightmassExporter()->SetVolumetricLightmapSettings(VolumetricLightmapSettings); const int32 NumTopLevelBricks = VolumetricLightmapSettings.TopLevelGridSize.X * VolumetricLightmapSettings.TopLevelGridSize.Y * VolumetricLightmapSettings.TopLevelGridSize.Z; int32 TargetNumVolumetricLightmapTasks; VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.VolumetricLightmaps"), TEXT("TargetNumVolumetricLightmapTasks"), TargetNumVolumetricLightmapTasks, GLightmassIni)); const int32 NumTasksPerTopLevelBrick = FMath::Clamp(TargetNumVolumetricLightmapTasks / NumTopLevelBricks, 1, VolumetricLightmapSettings.BrickSize * VolumetricLightmapSettings.BrickSize * VolumetricLightmapSettings.BrickSize); // Generate task guids for top level volumetric lightmap cells for (int32 VolumetricLightmapTaskIndex = 0; VolumetricLightmapTaskIndex < NumTopLevelBricks * NumTasksPerTopLevelBrick; VolumetricLightmapTaskIndex++) { Exporter->VolumetricLightmapTaskGuids.Add(FGuid::NewGuid(), VolumetricLightmapTaskIndex); } } TArray LevelsToRestore; // Hide all other lighting scenarios before we export skylights, which capture the rendered scene for (ULevel* Level : System.GetWorld()->GetLevels()) { if (Level->bIsVisible && !System.ShouldOperateOnLevel(Level)) { LevelsToRestore.Add(Level); EditorLevelUtils::SetLevelVisibilityTemporarily(Level, false); } } Exporter->WriteToChannel(Statistics, DebugMappingGuid); // Restore level state for (ULevel* Level : LevelsToRestore) { EditorLevelUtils::SetLevelVisibilityTemporarily(Level, true); } } bool FLightmassProcessor::ExecuteAmortizedMaterialExport() { FLightmassStatistics::FScopedGather ExportStatScope(Statistics.ExportTime); return Exporter->WriteToMaterialChannel(Statistics); } void FLightmassProcessor::IssueStaticShadowDepthMapTask(const ULightComponent* Light, int32 EstimatedCost) { if (Light->HasStaticShadowing() && !Light->HasStaticLighting()) { NSwarm::FTaskSpecification NewTaskSpecification(Light->LightGuid, TEXT("StaticShadowDepthMaps"), NSwarm::JOB_TASK_FLAG_USE_DEFAULTS ); NewTaskSpecification.Cost = EstimatedCost; int32 ErrorCode = Swarm.AddTask( NewTaskSpecification ); if( ErrorCode >= 0 ) { NumTotalSwarmTasks++; } else { UE_LOG(LogLightmassSolver, Warning, TEXT("Error, AddTask for StaticShadowDepthMaps failed with error code %d"), ErrorCode ); bProcessingFailed = true; } } } // Helper class for creating a list of paths relative to the engine directory, which can be queried as a list of raw strings. class FEngineDependencyPaths { public: FEngineDependencyPaths(std::initializer_list Paths) { Strings.Reserve(Paths.size()); for(const TCHAR* Path : Paths) { Strings.Add(FPaths::EngineDir() / Path); } RawStrings.Reserve(Strings.Num()); for(const FString& String : Strings) { RawStrings.Add(*String); } } const TCHAR** GetArray() const { return const_cast(RawStrings.GetData()); } int32 Num() const { return RawStrings.Num(); } private: TArray Strings; TArray RawStrings; }; bool FLightmassProcessor::BeginRun() { { FLightmassStatistics::FScopedGather ExportStatScope(Statistics.ExportTime); bool bGarbageCollectAfterExport = false; VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.StaticLighting"), TEXT("bGarbageCollectAfterExport"), bGarbageCollectAfterExport, GLightmassIni)); if (bGarbageCollectAfterExport == true) { CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS, true); } } double SwarmJobStartTime = FPlatformTime::Seconds(); VolumeSampleTaskCompleted = 0; MeshAreaLightDataTaskCompleted = 0; VolumeDistanceFieldTaskCompleted = 0; // Check if we can use 64-bit Lightmass. bool bUse64bitProcess = false; bool bAllow64bitProcess = true; VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.StaticLighting"), TEXT("bAllow64bitProcess"), bAllow64bitProcess, GLightmassIni)); if ( bAllow64bitProcess && FPlatformMisc::Is64bitOperatingSystem() ) { bUse64bitProcess = true; } // Setup dependencies for 32bit. static const FString LightmassExecutable32 = FPaths::EngineDir() / TEXT("Binaries/Win32/UnrealLightmass.exe"); static const FEngineDependencyPaths RequiredDependencyPaths32 = { TEXT("Binaries/DotNET/SwarmInterface.dll"), TEXT("Binaries/Win32/AgentInterface.dll"), TEXT("Binaries/Win32/UnrealLightmass-SwarmInterface.dll"), TEXT("Binaries/Win32/UnrealLightmass-ApplicationCore.dll"), TEXT("Binaries/Win32/UnrealLightmass-AutoRTFM.dll"), TEXT("Binaries/Win32/UnrealLightmass-Core.dll"), TEXT("Binaries/Win32/UnrealLightmass-CoreUObject.dll"), TEXT("Binaries/Win32/UnrealLightmass-CorePreciseFP.dll"), TEXT("Binaries/Win32/UnrealLightmass-DerivedDataCache.dll"), TEXT("Binaries/Win32/UnrealLightmass-DevHttp.dll"), TEXT("Binaries/Win32/UnrealLightmass-Sockets.dll"), TEXT("Binaries/Win32/UnrealLightmass-Zen.dll"), TEXT("Binaries/Win32/UnrealLightmass-Projects.dll"), TEXT("Binaries/Win32/UnrealLightmass-Json.dll"), TEXT("Binaries/Win32/UnrealLightmass-SSL.dll") TEXT("Binaries/Win32/UnrealLightmass-BuildSettings.dll") }; // Setup dependencies for 64bit. #if PLATFORM_WINDOWS static const FString LightmassExecutable64 = FPaths::EngineDir() / TEXT("Binaries/Win64/UnrealLightmass.exe"); static const FEngineDependencyPaths RequiredDependencyPaths64 = { TEXT("Binaries/DotNET/SwarmInterface.dll"), TEXT("Binaries/Win64/AgentInterface.dll"), TEXT("Binaries/Win64/UnrealLightmass-SwarmInterface.dll"), TEXT("Binaries/Win64/UnrealLightmass-ApplicationCore.dll"), TEXT("Binaries/Win64/UnrealLightmass-AutoRTFM.dll"), TEXT("Binaries/Win64/UnrealLightmass-Core.dll"), TEXT("Binaries/Win64/UnrealLightmass-CoreUObject.dll"), TEXT("Binaries/Win64/UnrealLightmass-CorePreciseFP.dll"), TEXT("Binaries/Win64/UnrealLightmass-DerivedDataCache.dll"), TEXT("Binaries/Win64/UnrealLightmass-DevHttp.dll"), TEXT("Binaries/Win64/UnrealLightmass-Sockets.dll"), TEXT("Binaries/Win64/UnrealLightmass-Zen.dll"), TEXT("Binaries/Win64/UnrealLightmass-Projects.dll"), TEXT("Binaries/Win64/UnrealLightmass-Json.dll"), TEXT("Binaries/Win64/UnrealLightmass-SSL.dll"), TEXT("Binaries/Win64/UnrealLightmass-BuildSettings.dll"), TEXT("Binaries/Win64/embree4.dll"), TEXT("Binaries/Win64/tbb12.dll"), TEXT("Binaries/Win64/tbbmalloc.dll") }; #elif PLATFORM_MAC static const FString LightmassExecutable64 = FPaths::EngineDir() / TEXT("Binaries/Mac/UnrealLightmass"); static const FEngineDependencyPaths RequiredDependencyPaths64 = { TEXT("Binaries/DotNET/Mac/AgentInterface.dll"), TEXT("Binaries/Mac/UnrealLightmass-ApplicationCore.dylib"), TEXT("Binaries/Mac/UnrealLightmass-AutoRTFM.dylib"), TEXT("Binaries/Mac/UnrealLightmass-Core.dylib"), TEXT("Binaries/Mac/UnrealLightmass-CoreUObject.dylib"), TEXT("Binaries/Mac/UnrealLightmass-CorePreciseFP.dylib"), TEXT("Binaries/Mac/UnrealLightmass-DerivedDataCache.dylib"), TEXT("Binaries/Mac/UnrealLightmass-DevHttp.dylib"), TEXT("Binaries/Mac/UnrealLightmass-Sockets.dylib"), TEXT("Binaries/Mac/UnrealLightmass-Zen.dylib"), TEXT("Binaries/Mac/UnrealLightmass-Json.dylib"), TEXT("Binaries/Mac/UnrealLightmass-Projects.dylib"), TEXT("Binaries/Mac/UnrealLightmass-SSL.dylib"), TEXT("Binaries/Mac/UnrealLightmass-SwarmInterface.dylib"), TEXT("Binaries/Mac/UnrealLightmass-BuildSettings.dylib"), TEXT("Binaries/Mac/libembree4.4.3.3.dylib"), TEXT("Binaries/Mac/libtbb.12.13.dylib"), TEXT("Binaries/Mac/libtbbmalloc.2.13.dylib") }; #elif PLATFORM_LINUX static const FString LightmassExecutable64 = FPaths::EngineDir() / TEXT("Binaries/Linux/UnrealLightmass"); static const FEngineDependencyPaths RequiredDependencyPaths64 = { TEXT("Binaries/DotNET/Linux/AgentInterface.dll"), TEXT("Binaries/Linux/libUnrealLightmass-ApplicationCore.so"), TEXT("Binaries/Linux/libUnrealLightmass-AutoRTFM.so"), TEXT("Binaries/Linux/libUnrealLightmass-Core.so"), TEXT("Binaries/Linux/libUnrealLightmass-CoreUObject.so"), TEXT("Binaries/Linux/libUnrealLightmass-CorePrecise.so"), TEXT("Binaries/Linux/libUnrealLightmass-DerivedDataCache.so"), TEXT("Binaries/Linux/libUnrealLightmass-DevHttp.so"), TEXT("Binaries/Linux/libUnrealLightmass-Sockets.so"), TEXT("Binaries/Linux/libUnrealLightmass-Zen.so"), TEXT("Binaries/Linux/libUnrealLightmass-Json.so"), TEXT("Binaries/Linux/libUnrealLightmass-Projects.so"), TEXT("Binaries/Linux/libUnrealLightmass-SSL.so"), TEXT("Binaries/Linux/libUnrealLightmass-SwarmInterface.so"), TEXT("Binaries/Linux/libUnrealLightmass-Networking.so"), TEXT("Binaries/Linux/libUnrealLightmass-Messaging.so"), TEXT("Binaries/Linux/libUnrealLightmass-BuildSettings.so"), TEXT("Plugins/Messaging/UdpMessaging/Binaries/Linux/libUnrealLightmass-UdpMessaging.so") }; #else // PLATFORM_LINUX #error "Unknown Lightmass platform" #endif // Set up optional dependencies. These might not exist in Launcher distributions, for example. const FEngineDependencyPaths OptionalDependencyPaths32 = { TEXT("Binaries/Win32/UnrealLightmass.pdb") }; const FEngineDependencyPaths OptionalDependencyPaths64 = { TEXT("Binaries/Win64/UnrealLightmass.pdb") }; // Set up the description for the Job const TCHAR* DescriptionKeys[] = { TEXT("MapName"), TEXT("GameName"), TEXT("QualityLevel") }; // Get the map name FString MapNameStr = System.GetWorld()->GetMapName(); const TCHAR* MapName = MapNameStr.GetCharArray().GetData(); // Get the game name const TCHAR* GameName = FApp::GetProjectName(); // Get the quality level TCHAR QualityLevel[MAX_SPRINTF] = TEXT(""); FCString::Sprintf( QualityLevel, TEXT("%d"), ( int32 )Exporter->QualityLevel ); const TCHAR* DescriptionValues[] = { MapName, GameName, QualityLevel }; // Create the job - one task per mapping. bProcessingSuccessful = false; bProcessingFailed = false; bQuitReceived = false; NumCompletedTasks = 0; bRunningLightmass = false; Statistics.SwarmJobOpenTime += FPlatformTime::Seconds() - SwarmJobStartTime; UE_LOG(LogLightmassSolver, Log, TEXT("Swarm launching: %s %s"), bUse64bitProcess ? *LightmassExecutable64 : *LightmassExecutable32, *Exporter->SceneGuid.ToString() ); SwarmJobStartTime = FPlatformTime::Seconds(); // If using Debug Mode (off by default), we use a hard-coded job GUID and Lightmass must be executed manually // (e.g. through a debugger), using the -debug command line parameter. // Lightmass will read all the cached input files and process the whole job locally without notifying // Swarm or Unreal that the job is completed. This also means that Lightmass can be executed as many times // as required (the input files will still be there in the Swarm cache) and Unreal doesn't need to be // running concurrently. int32 JobFlags = NSwarm::JOB_FLAG_USE_DEFAULTS; if (GLightmassDebugOptions.bDebugMode) { UE_LOG(LogLightmassSolver, Log, TEXT("Waiting for UnrealLightmass.exe to be launched manually...") ); UE_LOG(LogLightmassSolver, Log, TEXT("Note: This Job will not be distributed") ); JobFlags |= NSwarm::JOB_FLAG_MANUAL_START; } else { // Enable Swarm Job distribution, if requested if (GSwarmDebugOptions.bDistributionEnabled) { UE_LOG(LogLightmassSolver, Log, TEXT("Swarm will be allowed to distribute this job") ); JobFlags |= NSwarm::JOB_FLAG_ALLOW_REMOTE; } else { UE_LOG(LogLightmassSolver, Log, TEXT("Swarm will be not be allowed to distribute this job; it will run locally only") ); } } // Check to see if swarm should be run minimized (it should by default) bool bMinimizeSwarm = true; GConfig->GetBool(TEXT("LightingBuildOptions"), TEXT("MinimizeSwarm"), bMinimizeSwarm, GEditorSettingsIni); if ( bMinimizeSwarm ) { UE_LOG(LogLightmassSolver, Log, TEXT("Swarm will be run minimized") ); JobFlags |= NSwarm::JOB_FLAG_MINIMIZED; } FString CommandLineParameters = *Exporter->SceneGuid.ToString(); if (GLightmassDebugOptions.bStatsEnabled) { CommandLineParameters += TEXT(" -stats"); } NSwarm::FJobSpecification JobSpecification32, JobSpecification64; if ( !bUse64bitProcess ) { JobSpecification32 = NSwarm::FJobSpecification( *LightmassExecutable32, *CommandLineParameters, ( NSwarm::TJobTaskFlags )JobFlags ); JobSpecification32.AddDependencies( RequiredDependencyPaths32.GetArray(), RequiredDependencyPaths32.Num(), OptionalDependencyPaths32.GetArray(), OptionalDependencyPaths32.Num() ); JobSpecification32.AddDescription( DescriptionKeys, DescriptionValues, UE_ARRAY_COUNT(DescriptionKeys) ); } else { JobSpecification64 = NSwarm::FJobSpecification( *LightmassExecutable64, *CommandLineParameters, ( NSwarm::TJobTaskFlags )JobFlags ); JobSpecification64.AddDependencies( RequiredDependencyPaths64.GetArray(), RequiredDependencyPaths64.Num(), OptionalDependencyPaths64.GetArray(), OptionalDependencyPaths64.Num() ); JobSpecification64.AddDescription( DescriptionKeys, DescriptionValues, UE_ARRAY_COUNT(DescriptionKeys) ); } int32 ErrorCode = Swarm.BeginJobSpecification( JobSpecification32, JobSpecification64 ); if( ErrorCode < 0 ) { UE_LOG(LogLightmassSolver, Log, TEXT("Error, BeginJobSpecification failed with error code %d"), ErrorCode ); bProcessingFailed = true; } // Count the number of tasks given to Swarm NumTotalSwarmTasks = 0; if (System.GetWorld()->GetWorldSettings()->bPrecomputeVisibility) { for (int32 TaskIndex = 0; TaskIndex < Exporter->VisibilityBucketGuids.Num(); TaskIndex++) { NSwarm::FTaskSpecification NewTaskSpecification( Exporter->VisibilityBucketGuids[TaskIndex], TEXT("PrecomputedVisibility"), NSwarm::JOB_TASK_FLAG_USE_DEFAULTS ); //@todo - accurately estimate cost NewTaskSpecification.Cost = 10000; ErrorCode = Swarm.AddTask( NewTaskSpecification ); if( ErrorCode >= 0 ) { NumTotalSwarmTasks++; } else { UE_LOG(LogLightmassSolver, Log, TEXT("Error, AddTask failed with error code %d"), ErrorCode ); } } } if (!bOnlyBuildVisibility) { const EVolumeLightingMethod VolumeLightingMethod = System.GetWorld()->GetWorldSettings()->LightmassSettings.VolumeLightingMethod; if (VolumeLightingMethod == VLM_VolumetricLightmap) { for (TMap::TIterator It(Exporter->VolumetricLightmapTaskGuids); It; ++It ) { NSwarm::FTaskSpecification NewTaskSpecification(It.Key(), TEXT("VolumetricLightmap"), NSwarm::JOB_TASK_FLAG_USE_DEFAULTS ); //@todo - accurately estimate cost NewTaskSpecification.Cost = 10000; ErrorCode = Swarm.AddTask( NewTaskSpecification ); UE_LOG(LogLightmassSolver, Verbose, TEXT("AddTask VLM %s"), *It.Key().ToString() ); if( ErrorCode >= 0 ) { NumTotalSwarmTasks++; } else { UE_LOG(LogLightmassSolver, Warning, TEXT("Error, AddTask failed with error code %d"), ErrorCode ); bProcessingFailed = true; } } } else { check(VolumeLightingMethod == VLM_SparseVolumeLightingSamples); NSwarm::FTaskSpecification NewTaskSpecification( Lightmass::PrecomputedVolumeLightingGuid, TEXT("VolumeSamples"), NSwarm::JOB_TASK_FLAG_USE_DEFAULTS ); //@todo - accurately estimate cost // Changed estimated cost: this should be the maximum cost, because it became really big if there are WORLD_MAX size light-mapping NewTaskSpecification.Cost = INT_MAX; ErrorCode = Swarm.AddTask( NewTaskSpecification ); if( ErrorCode >= 0 ) { NumTotalSwarmTasks++; } else { UE_LOG(LogLightmassSolver, Warning, TEXT("Error, AddTask failed with error code %d"), ErrorCode ); bProcessingFailed = true; } } { NSwarm::FTaskSpecification NewTaskSpecification( Lightmass::MeshAreaLightDataGuid, TEXT("MeshAreaLightData"), NSwarm::JOB_TASK_FLAG_USE_DEFAULTS ); NewTaskSpecification.Cost = 1000; ErrorCode = Swarm.AddTask( NewTaskSpecification ); UE_LOG(LogLightmassSolver, Verbose, TEXT("AddTask MeshAreasLights %s"), *Lightmass::MeshAreaLightDataGuid.ToString()); if( ErrorCode >= 0 ) { NumTotalSwarmTasks++; } else { UE_LOG(LogLightmassSolver, Log, TEXT("Error, AddTask failed with error code %d"), ErrorCode ); bProcessingFailed = true; } } { for (int32 LightIndex = 0; LightIndex < Exporter->DirectionalLights.Num(); LightIndex++) { const ULightComponent* Light = Exporter->DirectionalLights[LightIndex]; IssueStaticShadowDepthMapTask(Light, INT_MAX); } for (int32 LightIndex = 0; LightIndex < Exporter->SpotLights.Num(); LightIndex++) { const ULightComponent* Light = Exporter->SpotLights[LightIndex]; IssueStaticShadowDepthMapTask(Light, 10000); } for (int32 LightIndex = 0; LightIndex < Exporter->PointLights.Num(); LightIndex++) { const ULightComponent* Light = Exporter->PointLights[LightIndex]; IssueStaticShadowDepthMapTask(Light, 10000); } for (int32 LightIndex = 0; LightIndex < Exporter->RectLights.Num(); LightIndex++) { const ULightComponent* Light = Exporter->RectLights[LightIndex]; IssueStaticShadowDepthMapTask(Light, 10000); } } // Add BSP mapping tasks. for( int32 MappingIdx=0; (ErrorCode >= 0) && MappingIdx < Exporter->BSPSurfaceMappings.Num() && !GEditor->GetMapBuildCancelled(); MappingIdx++ ) { FBSPSurfaceStaticLighting* BSPMapping = Exporter->BSPSurfaceMappings[MappingIdx]; if (BSPMapping->bProcessMapping == true) { PendingBSPMappings.Add(BSPMapping->GetLightingGuid(), BSPMapping); NSwarm::FTaskSpecification NewTaskSpecification( BSPMapping->GetLightingGuid(), TEXT("BSPMapping"), NSwarm::JOB_TASK_FLAG_USE_DEFAULTS ); NewTaskSpecification.Cost = BSPMapping->GetTexelCount(); ErrorCode = Swarm.AddTask( NewTaskSpecification ); if( ErrorCode >= 0 ) { NumTotalSwarmTasks++; } else { UE_LOG(LogLightmassSolver, Log, TEXT("Error, AddTask failed with error code %d"), ErrorCode ); } } } // Add static mesh texture mappings tasks. for( int32 MappingIdx=0; (ErrorCode >= 0) && MappingIdx < Exporter->StaticMeshTextureMappings.Num() && !GEditor->GetMapBuildCancelled(); MappingIdx++ ) { FStaticMeshStaticLightingTextureMapping* SMTextureMapping = Exporter->StaticMeshTextureMappings[MappingIdx]; if (SMTextureMapping->bProcessMapping == true) { PendingTextureMappings.Add(SMTextureMapping->GetLightingGuid(), SMTextureMapping); NSwarm::FTaskSpecification NewTaskSpecification( SMTextureMapping->GetLightingGuid(), TEXT("SMTextureMapping"), NSwarm::JOB_TASK_FLAG_USE_DEFAULTS ); NewTaskSpecification.Cost = SMTextureMapping->GetTexelCount(); ErrorCode = Swarm.AddTask( NewTaskSpecification ); UE_LOG(LogLightmassSolver, Verbose, TEXT("AddTask Mapping %s"), *SMTextureMapping->GetLightingGuid().ToString()); if( ErrorCode >= 0 ) { NumTotalSwarmTasks++; } else { UE_LOG(LogLightmassSolver, Log, TEXT("Error, AddTask failed with error code %d"), ErrorCode ); } } } // Add Landscape mapping tasks. for( int32 MappingIdx=0; (ErrorCode >= 0) && MappingIdx < Exporter->LandscapeTextureMappings.Num() && !GEditor->GetMapBuildCancelled(); MappingIdx++ ) { FLandscapeStaticLightingTextureMapping* LandscapeMapping = Exporter->LandscapeTextureMappings[MappingIdx]; if (LandscapeMapping->bProcessMapping == true) { PendingLandscapeMappings.Add(LandscapeMapping->GetLightingGuid(), LandscapeMapping); NSwarm::FTaskSpecification NewTaskSpecification( LandscapeMapping->GetLightingGuid(), TEXT("LandscapeMapping"), NSwarm::JOB_TASK_FLAG_USE_DEFAULTS ); NewTaskSpecification.Cost = LandscapeMapping->GetTexelCount(); ErrorCode = Swarm.AddTask( NewTaskSpecification ); UE_LOG(LogLightmassSolver, Verbose, TEXT("AddTask Landscape Mapping %s"), *LandscapeMapping->GetLightingGuid().ToString()); if( ErrorCode >= 0 ) { NumTotalSwarmTasks++; } else { UE_LOG(LogLightmassSolver, Log, TEXT("Error, AddTask failed with error code %d"), ErrorCode ); } } } } int32 EndJobErrorCode = Swarm.EndJobSpecification(); if( EndJobErrorCode < 0 ) { UE_LOG(LogLightmassSolver, Log, TEXT("Error, EndJobSpecification failed with error code %d"), ErrorCode ); } if ( ErrorCode < 0 || EndJobErrorCode < 0 ) { bProcessingFailed = true; } int32 NumTotalTasks = NumTotalSwarmTasks; // In deterministic mode, we import and process the mappings after Lightmass is done, so we have twice the steps. NumTotalTasks *= 2; GWarn->StatusUpdate( NumCompletedTasks, NumTotalTasks, LOCTEXT("BeginRunningLightmassTask", "Running Lightmass...") ); Statistics.SwarmJobOpenTime += FPlatformTime::Seconds() - SwarmJobStartTime; LightmassStartTime = FPlatformTime::Seconds(); #if USE_LOCAL_SWARM_INTERFACE // @todo Mac: add proper progress reporting from UnrealLightmass bProcessingSuccessful = true; #endif return !bProcessingFailed; } int32 FLightmassProcessor::GetAsyncPercentDone() const { return NumCompletedTasks * 100 / NumTotalSwarmTasks; } float FLightmassProcessor::GetAmortizedExportPercentDone() const { return Exporter->GetAmortizedExportPercentDone(); } bool FLightmassProcessor::Update() { bool bIsFinished = false; if ( !bQuitReceived && !bProcessingFailed && !GEditor->GetMapBuildCancelled() ) { bool bAllTaskAreComplete = (FPlatformAtomics::AtomicRead(&NumCompletedTasks) == NumTotalSwarmTasks ? true : false); #if USE_LOCAL_SWARM_INTERFACE if (IsRunningCommandlet()) { FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread); } #endif GLog->Flush(); bIsFinished = bAllTaskAreComplete && bProcessingSuccessful; if (bIsFinished) { Statistics.LightmassTime += FPlatformTime::Seconds() - LightmassStartTime; } } else { bIsFinished = true; } ProcessAlertMessages(); #if USE_LOCAL_SWARM_INTERFACE int32 Status = 0; const bool bIsLightmassRunning = Swarm.IsJobProcessRunning(&Status); if (!bIsLightmassRunning) { bIsFinished = true; bProcessingFailed = Status != 0; bProcessingSuccessful = !bProcessingFailed; } #endif return bIsFinished; } void FLightmassProcessor::ImportDeferredMappings() { TArray Files; IFileManager::Get().FindFiles(Files, *DeferredMappingsDirectory, TEXT(".lm")); DeferredMappings = new FDeferredMappingsBundle; // load the mappings for(const FString& FileName : Files) { TArray FileData; int32 BaseMappingIndex = DeferredMappings->Mappings.Num(); FString FullFileName = FString::Printf(TEXT("%s\\%s"), *DeferredMappingsDirectory, *FileName); if (FFileHelper::LoadFileToArray(FileData, *FullFileName)) { FObjectMemoryReader Reader(FileData); FDeferredMappingsBundle Bundle; Bundle.Serialize(Reader, false); for (FLightmassProcessor::FTextureMappingImportHelper& Mapping : Bundle.Mappings) { // Those mapping guid are just numbered 0 to n, so we rebase them to be unique in the combined mappings Mapping.MappingGuid.D += BaseMappingIndex; } // transfer all bundle mappings DeferredMappings->Mappings.Append(Bundle.Mappings); } else { UE_LOG(LogLightmassSolver, Warning, TEXT("Error, couldn't import deferred mapping %s"), *FullFileName); bProcessingFailed = true; } } DeferredMappings->MapHelpers(); for (FLightmassProcessor::FTextureMappingImportHelper& Mapping : DeferredMappings->Mappings) { ImportedMappings.Add(Mapping.MappingGuid, &Mapping); } } void FLightmassProcessor::ClearImportedDeferredMappings() { if (DeferredMappings) { for (FLightmassProcessor::FTextureMappingImportHelper& Mapping : DeferredMappings->Mappings) { ImportedMappings.Remove(Mapping.MappingGuid); } } } void FLightmassProcessor::ExportDeferredMappings() { if (DeferredMappings && !System.Options.bApplyDeferedActorMappingPass) { FGuid BundleGuid = FGuid::NewGuid(); FString FileName = FString::Printf(TEXT("Mappings_%s.lm"), *BundleGuid.ToString()); TArray FileData; FObjectMemoryWriter Writer(FileData); DeferredMappings->Serialize(Writer, false); FString FullFileName = FString::Printf(TEXT("%s\\%s"), *DeferredMappingsDirectory, *FileName); if (!FFileHelper::SaveArrayToFile(FileData, *FullFileName)) { UE_LOG(LogLightmassSolver, Warning, TEXT("Error, couldn't export deferred mapping %s"), *FullFileName); bProcessingFailed = true; } } } bool FLightmassProcessor::IsDeferredMapping(const FGuid& Guid) { if (const FStaticLightingMapping* Mapping = Exporter->FindMappingByGuid(Guid)) { return Mapping->IsDeferred(); } return false; } void FLightmassProcessor::DeferMapping(FTextureMappingImportHelper* ImportHelper) { if (!DeferredMappings) { DeferredMappings = new FDeferredMappingsBundle(); } DeferredMappings->Mappings.Add(*ImportHelper); DeferredMappings->OwnerToMapping.Add(ImportHelper->OwnerGuid, ImportHelper->MappingGuid); } bool FLightmassProcessor::CompleteRun() { bRunningLightmass = false; double ImportStartTime = FPlatformTime::Seconds(); double OriginalApplyTime = Statistics.ApplyTimeInProcessing; if ( !bProcessingFailed && !GEditor->GetMapBuildCancelled() ) { ImportVolumeSamples(); if (Exporter->VolumetricLightmapTaskGuids.Num() > 0) { ImportVolumetricLightmap(); } ImportPrecomputedVisibility(); ImportMeshAreaLightData(); ImportVolumeDistanceFieldData(); if (bImportCompletedMappingsImmediately) { if (System.Options.bApplyDeferedActorMappingPass) { ImportDeferredMappings(); } // Import any outstanding completed mappings. ImportMappings(false); { // Detach all components // This must be done globally because different mappings will FGlobalComponentRecreateRenderStateContext ReregisterContext; // Block until the RT processes the unregister before modifying variables that it may need to access FlushRenderingCommands(); ProcessAvailableMappings(); } // Export mapping which we'll not process on this run (this will remove then from available mappings) ExportDeferredMappings(); } ApplyPrecomputedVisibility(); ProcessAlertMessages(); } CompletedMappingTasks.Clear(); CompletedVisibilityTasks.Clear(); CompletedVolumetricLightmapTasks.Clear(); ClearImportedDeferredMappings(); double ApplyTimeDelta = Statistics.ApplyTimeInProcessing - OriginalApplyTime; Statistics.ImportTimeInProcessing += FPlatformTime::Seconds() - ImportStartTime - ApplyTimeDelta; return bProcessingSuccessful; } bool FLightmassProcessor::IsProcessingCompletedSuccessfully() const { return bProcessingSuccessful && !bQuitReceived && !bProcessingFailed && !GEditor->GetMapBuildCancelled(); } /** * Import all mappings that have been completed so far. * @param bProcessImmediately If true, immediately process the mapping * If false, store it off for later processing */ void FLightmassProcessor::ImportMappings(bool bProcessImmediately) { // This will return a list of all the completed Guids TList* Element = CompletedMappingTasks.ExtractAll(); // Reverse the list, so we have the mappings in the same order that they came in { TList* PrevElement = NULL; TList* NextElement = NULL; while (Element) { NextElement = Element->Next; Element->Next = PrevElement; PrevElement = Element; Element = NextElement; } // Assign the new head of the list Element = PrevElement; } while ( Element ) { TList* NextElement = Element->Next; ImportMapping( Element->Element, bProcessImmediately ); delete Element; Element = NextElement; } } static_assert(LM_NUM_SH_COEFFICIENTS == NUM_INDIRECT_LIGHTING_SH_COEFFICIENTS, "Lightmass SH generation must match engine SH expectations."); /** Imports volume lighting samples from Lightmass and adds them to the appropriate levels. */ void FLightmassProcessor::ImportVolumeSamples() { if (VolumeSampleTaskCompleted > 0) { { static_assert(sizeof(FDebugVolumeLightingSample) == sizeof(Lightmass::FDebugVolumeLightingSample), "Debug type sizes must match."); const FString ChannelName = Lightmass::CreateChannelName(Lightmass::VolumeLightingDebugOutputGuid, Lightmass::LM_VOLUMEDEBUGOUTPUT_VERSION, Lightmass::LM_VOLUMEDEBUGOUTPUT_EXTENSION); const int32 Channel = Swarm.OpenChannel( *ChannelName, LM_VOLUMEDEBUGOUTPUT_CHANNEL_FLAGS ); if (Channel >= 0) { ReadArray(Channel, GDebugStaticLightingInfo.VolumeLightingSamples); Swarm.CloseChannel(Channel); } } const FString ChannelName = Lightmass::CreateChannelName(Lightmass::PrecomputedVolumeLightingGuid, Lightmass::LM_VOLUMESAMPLES_VERSION, Lightmass::LM_VOLUMESAMPLES_EXTENSION); const int32 Channel = Swarm.OpenChannel( *ChannelName, LM_VOLUMESAMPLES_CHANNEL_FLAGS ); if (Channel >= 0) { FVector4f UnusedVolumeCenter; Swarm.ReadChannel(Channel, &UnusedVolumeCenter, sizeof(UnusedVolumeCenter)); FVector4f UnusedVolumeExtent; Swarm.ReadChannel(Channel, &UnusedVolumeExtent, sizeof(UnusedVolumeExtent)); int32 NumStreamLevels = System.GetWorld()->GetStreamingLevels().Num(); int32 NumVolumeSampleArrays; Swarm.ReadChannel(Channel, &NumVolumeSampleArrays, sizeof(NumVolumeSampleArrays)); for (int32 ArrayIndex = 0; ArrayIndex < NumVolumeSampleArrays; ArrayIndex++) { FGuid LevelGuid; Swarm.ReadChannel(Channel, &LevelGuid, sizeof(LevelGuid)); TArray VolumeSamples; ReadArray(Channel, VolumeSamples); ULevel* CurrentLevel = System.LightingContext.GetLevelForGuid(LevelGuid).Get(); // Only build precomputed light for visible streamed levels if (CurrentLevel && CurrentLevel->bIsVisible) { UMapBuildDataRegistry* CurrentRegistry = System.LightingContext.GetOrCreateRegistryForLevel(CurrentLevel); FPrecomputedLightVolumeData& CurrentLevelData = CurrentRegistry->AllocateLevelPrecomputedLightVolumeBuildData(CurrentLevel->LevelBuildDataId); FBox3f LevelVolumeBounds(ForceInit); for (int32 SampleIndex = 0; SampleIndex < VolumeSamples.Num(); SampleIndex++) { const Lightmass::FVolumeLightingSampleData& CurrentSample = VolumeSamples[SampleIndex]; FVector4f SampleMin = CurrentSample.PositionAndRadius - FVector3f(CurrentSample.PositionAndRadius.W); FVector4f SampleMax = CurrentSample.PositionAndRadius + FVector3f(CurrentSample.PositionAndRadius.W); LevelVolumeBounds += FBox3f(SampleMin, SampleMax); } CurrentLevelData.Initialize(FBox(LevelVolumeBounds)); for (int32 SampleIndex = 0; SampleIndex < VolumeSamples.Num(); SampleIndex++) { const Lightmass::FVolumeLightingSampleData& CurrentSample = VolumeSamples[SampleIndex]; FVolumeLightingSample NewHighQualitySample; NewHighQualitySample.Position = FVector4f(CurrentSample.PositionAndRadius); NewHighQualitySample.Radius = CurrentSample.PositionAndRadius.W; NewHighQualitySample.SetPackedSkyBentNormal(FVector3d(CurrentSample.SkyBentNormal)); NewHighQualitySample.DirectionalLightShadowing = CurrentSample.DirectionalLightShadowing; for (int32 CoefficientIndex = 0; CoefficientIndex < NUM_INDIRECT_LIGHTING_SH_COEFFICIENTS; CoefficientIndex++) { NewHighQualitySample.Lighting.R.V[CoefficientIndex] = CurrentSample.HighQualityCoefficients[CoefficientIndex][0]; NewHighQualitySample.Lighting.G.V[CoefficientIndex] = CurrentSample.HighQualityCoefficients[CoefficientIndex][1]; NewHighQualitySample.Lighting.B.V[CoefficientIndex] = CurrentSample.HighQualityCoefficients[CoefficientIndex][2]; } FVolumeLightingSample NewLowQualitySample; NewLowQualitySample.Position = FVector4f(CurrentSample.PositionAndRadius); NewLowQualitySample.Radius = CurrentSample.PositionAndRadius.W; NewLowQualitySample.DirectionalLightShadowing = CurrentSample.DirectionalLightShadowing; NewLowQualitySample.SetPackedSkyBentNormal(FVector3d(CurrentSample.SkyBentNormal)); for (int32 CoefficientIndex = 0; CoefficientIndex < NUM_INDIRECT_LIGHTING_SH_COEFFICIENTS; CoefficientIndex++) { NewLowQualitySample.Lighting.R.V[CoefficientIndex] = CurrentSample.LowQualityCoefficients[CoefficientIndex][0]; NewLowQualitySample.Lighting.G.V[CoefficientIndex] = CurrentSample.LowQualityCoefficients[CoefficientIndex][1]; NewLowQualitySample.Lighting.B.V[CoefficientIndex] = CurrentSample.LowQualityCoefficients[CoefficientIndex][2]; } CurrentLevelData.AddHighQualityLightingSample(NewHighQualitySample); CurrentLevelData.AddLowQualityLightingSample(NewLowQualitySample); } CurrentLevelData.FinalizeSamples(); } } Swarm.CloseChannel(Channel); } else { UE_LOG(LogLightmassSolver, Log, TEXT("Error, OpenChannel failed to open %s with error code %d"), *ChannelName, Channel ); } FPlatformAtomics::InterlockedExchange(&VolumeSampleTaskCompleted, 0); } } /** Imports precomputed visibility */ void FLightmassProcessor::ImportPrecomputedVisibility() { TList* Element = CompletedVisibilityTasks.ExtractAll(); // Reverse the list, so we have the tasks in the same order that they came in { TList* PrevElement = NULL; TList* NextElement = NULL; while (Element) { NextElement = Element->Next; Element->Next = PrevElement; PrevElement = Element; Element = NextElement; } // Assign the new head of the list Element = PrevElement; } while ( Element ) { // If this task has not already been imported, import it now TList* NextElement = Element->Next; const FString ChannelName = Lightmass::CreateChannelName(Element->Element, Lightmass::LM_PRECOMPUTEDVISIBILITY_VERSION, Lightmass::LM_PRECOMPUTEDVISIBILITY_EXTENSION); const int32 Channel = Swarm.OpenChannel( *ChannelName, LM_PRECOMPUTEDVISIBILITY_CHANNEL_FLAGS ); if (Channel >= 0) { // Find the index of this visibility task in VisibilityBucketGuids const int32 ArrayIndex = Exporter->VisibilityBucketGuids.Find(Element->Element); check(ArrayIndex >= 0); if (CompletedPrecomputedVisibilityCells.Num() == 0) { CompletedPrecomputedVisibilityCells.AddZeroed(Exporter->VisibilityBucketGuids.Num()); } int32 NumCells = 0; Swarm.ReadChannel(Channel, &NumCells, sizeof(NumCells)); for (int32 CellIndex = 0; CellIndex < NumCells; CellIndex++) { FBox3f Bounds; Swarm.ReadChannel(Channel, &Bounds, sizeof(Bounds)); // Use the same index for this task guid as it has in VisibilityBucketGuids, so that visibility cells are processed in a deterministic order CompletedPrecomputedVisibilityCells[ArrayIndex].AddZeroed(); FUncompressedPrecomputedVisibilityCell& CurrentCell = CompletedPrecomputedVisibilityCells[ArrayIndex].Last(); CurrentCell.Bounds = FBox(Bounds); ReadArray(Channel, CurrentCell.VisibilityData); } TArray DebugRays; ReadArray(Channel, DebugRays); GDebugStaticLightingInfo.PrecomputedVisibilityRays.Append(DebugRays); Swarm.CloseChannel(Channel); } else { UE_LOG(LogLightmassSolver, Log, TEXT("Error, OpenChannel failed to open %s with error code %d"), *ChannelName, Channel ); } delete Element; Element = NextElement; } } static bool IsMeshVisible(const TArray& VisibilityData, int32 MeshId) { return (VisibilityData[MeshId / 8] & 1 << (MeshId % 8)) != 0; } static int32 AccumulateVisibility(const TArray& OtherCellData, TArray& CellData) { int32 NumAdded = 0; checkSlow(OtherCellData.Num() == CellData.Num()); for (int32 i = 0; i < OtherCellData.Num(); i++) { if (OtherCellData[i] != 0) { for (int32 BitIndex = 0; BitIndex < 8; BitIndex++) { NumAdded += IsMeshVisible(OtherCellData, i * 8 + BitIndex) && !IsMeshVisible(CellData, i * 8 + BitIndex) ? 1 : 0; } } CellData[i] |= OtherCellData[i]; } return NumAdded; } struct FPrecomputedVisibilitySortGridCell { TArray> Cells; }; void SpreadVisibilityCell( float CellSize, float PlayAreaHeight, const FUncompressedPrecomputedVisibilityCell& OtherCell, FUncompressedPrecomputedVisibilityCell& VisibilityCell, int32& QueriesVisibleFromSpreadingNeighbors) { // Determine whether the cell is a world space neighbor if (!(OtherCell.Bounds.Min == VisibilityCell.Bounds.Min && OtherCell.Bounds.Max == VisibilityCell.Bounds.Max) && FMath::Abs(VisibilityCell.Bounds.Min.X - OtherCell.Bounds.Min.X) < CellSize + KINDA_SMALL_NUMBER && FMath::Abs(VisibilityCell.Bounds.Min.Y - OtherCell.Bounds.Min.Y) < CellSize + KINDA_SMALL_NUMBER // Don't spread from cells below, they're probably below the ground and see too much && OtherCell.Bounds.Min.Z - VisibilityCell.Bounds.Min.Z > -PlayAreaHeight * 0.5f // Only spread from one cell above && OtherCell.Bounds.Min.Z - VisibilityCell.Bounds.Min.Z < PlayAreaHeight * 1.5f) { // Combine the neighbor's visibility with the current cell's visibility // This reduces visibility errors at the cost of less effective culling QueriesVisibleFromSpreadingNeighbors += AccumulateVisibility(OtherCell.VisibilityData, VisibilityCell.VisibilityData); } } void FLightmassProcessor::ApplyPrecomputedVisibility() { TArray CombinedPrecomputedVisibilityCells; for (int32 ArrayIndex = 0; ArrayIndex < CompletedPrecomputedVisibilityCells.Num(); ArrayIndex++) { CombinedPrecomputedVisibilityCells.Append(CompletedPrecomputedVisibilityCells[ArrayIndex]); } CompletedPrecomputedVisibilityCells.Empty(); if (CombinedPrecomputedVisibilityCells.Num() > 0) { const double StartTime = FPlatformTime::Seconds(); int32 VisibilitySpreadingIterations; const TCHAR* AggressivenessSectionNames[VIS_Max] = { TEXT("DevOptions.PrecomputedVisibility"), TEXT("DevOptions.PrecomputedVisibilityModeratelyAggressive"), TEXT("DevOptions.PrecomputedVisibilityMostAggressive")}; const TCHAR* ActiveSection = AggressivenessSectionNames[FMath::Clamp((int32)System.GetWorld()->GetWorldSettings()->VisibilityAggressiveness, 0, VIS_Max - 1)]; VERIFYLIGHTMASSINI(GConfig->GetInt(ActiveSection, TEXT("VisibilitySpreadingIterations"), VisibilitySpreadingIterations, GLightmassIni)); bool bCompressVisibilityData; VERIFYLIGHTMASSINI(GConfig->GetBool(TEXT("DevOptions.PrecomputedVisibility"), TEXT("bCompressVisibilityData"), bCompressVisibilityData, GLightmassIni)); const float CellSize = System.GetWorld()->GetWorldSettings()->VisibilityCellSize; float PlayAreaHeight = 0; VERIFYLIGHTMASSINI(GConfig->GetFloat(TEXT("DevOptions.PrecomputedVisibility"), TEXT("PlayAreaHeight"), PlayAreaHeight, GLightmassIni)); int32 CellBucketSize = 0; VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.PrecomputedVisibility"), TEXT("CellRenderingBucketSize"), CellBucketSize, GLightmassIni)); int32 NumCellBuckets = 0; VERIFYLIGHTMASSINI(GConfig->GetInt(TEXT("DevOptions.PrecomputedVisibility"), TEXT("NumCellRenderingBuckets"), NumCellBuckets, GLightmassIni)); int32 TotalNumQueries = 0; int32 QueriesVisibleFromSpreadingNeighbors = 0; for (int32 IterationIndex = 0; IterationIndex < VisibilitySpreadingIterations; IterationIndex++) { FBox AllCellsBounds(ForceInit); for (int32 CellIndex = 0; CellIndex < CombinedPrecomputedVisibilityCells.Num(); CellIndex++) { AllCellsBounds += CombinedPrecomputedVisibilityCells[CellIndex].Bounds; } int32 GridSizeX = FMath::TruncToInt(AllCellsBounds.GetSize().X / CellSize + .5f); int32 GridSizeY = FMath::TruncToInt(AllCellsBounds.GetSize().Y / CellSize + .5f); const int32 GridSizeMax = 10000; if (GridSizeX < GridSizeMax && GridSizeY < GridSizeMax) { TArray SortGrid; SortGrid.Empty(GridSizeX * GridSizeY); SortGrid.AddZeroed(GridSizeX * GridSizeY); // Add visibility cells into a 2d grid // Note that visibility data is duplicated so that the next pass can read from original neighbor visibility data for (int32 CellIndex = 0; CellIndex < CombinedPrecomputedVisibilityCells.Num(); CellIndex++) { float CellXFloat = (CombinedPrecomputedVisibilityCells[CellIndex].Bounds.GetCenter().X - AllCellsBounds.Min.X) / CellSize; int32 CellX = FMath::Clamp(FMath::TruncToInt(CellXFloat), 0, GridSizeX - 1); float CellYFloat = (CombinedPrecomputedVisibilityCells[CellIndex].Bounds.GetCenter().Y - AllCellsBounds.Min.Y) / CellSize; int32 CellY = FMath::Clamp(FMath::TruncToInt(CellYFloat), 0, GridSizeY - 1); FPrecomputedVisibilitySortGridCell& GridCell = SortGrid[CellY * GridSizeX + CellX]; GridCell.Cells.Add(CombinedPrecomputedVisibilityCells[CellIndex]); } // Gather visibility from neighbors, using the 2d grid to accelerate the neighbor search for (int32 CellIndex = 0; CellIndex < CombinedPrecomputedVisibilityCells.Num(); CellIndex++) { FUncompressedPrecomputedVisibilityCell& CurrentCell = CombinedPrecomputedVisibilityCells[CellIndex]; float CellXFloat = (CombinedPrecomputedVisibilityCells[CellIndex].Bounds.GetCenter().X - AllCellsBounds.Min.X) / CellSize; int32 CellX = FMath::Clamp(FMath::TruncToInt(CellXFloat), 0, GridSizeX - 1); float CellYFloat = (CombinedPrecomputedVisibilityCells[CellIndex].Bounds.GetCenter().Y - AllCellsBounds.Min.Y) / CellSize; int32 CellY = FMath::Clamp(FMath::TruncToInt(CellYFloat), 0, GridSizeY - 1); TotalNumQueries += CurrentCell.VisibilityData.Num() * 8; const FPrecomputedVisibilitySortGridCell& GridCell = SortGrid[CellY * GridSizeX + CellX]; for (int32 YOffset = -1; YOffset <= 1; YOffset++) { for (int32 XOffset = -1; XOffset <= 1; XOffset++) { int32 FinalCellX = CellX + XOffset; int32 FinalCellY = CellY + YOffset; if (FinalCellX >= 0 && FinalCellX < GridSizeX && FinalCellY >= 0 && FinalCellY < GridSizeY) { const TArray>& CurrentSortCell = SortGrid[FinalCellY * GridSizeX + FinalCellX].Cells; for (int32 VisibilityCellIndex = 0; VisibilityCellIndex < CurrentSortCell.Num(); VisibilityCellIndex++) { const FUncompressedPrecomputedVisibilityCell& OtherCell = CurrentSortCell[VisibilityCellIndex]; SpreadVisibilityCell(CellSize, PlayAreaHeight, OtherCell, CurrentCell, QueriesVisibleFromSpreadingNeighbors); } } } } } } else { // Brute force O(N^2) neighbor spreading version // Copy the original data since we read from outside the current cell TArray OriginalPrecomputedVisibilityCells(CombinedPrecomputedVisibilityCells); for (int32 CellIndex = 0; CellIndex < CombinedPrecomputedVisibilityCells.Num(); CellIndex++) { FUncompressedPrecomputedVisibilityCell& CurrentCell = CombinedPrecomputedVisibilityCells[CellIndex]; TotalNumQueries += CurrentCell.VisibilityData.Num() * 8; for (int32 OtherCellIndex = 0; OtherCellIndex < OriginalPrecomputedVisibilityCells.Num(); OtherCellIndex++) { const FUncompressedPrecomputedVisibilityCell& OtherCell = OriginalPrecomputedVisibilityCells[OtherCellIndex]; SpreadVisibilityCell(CellSize, PlayAreaHeight, OtherCell, CurrentCell, QueriesVisibleFromSpreadingNeighbors); } } } } const FVector2D CellBucketOriginXY(CombinedPrecomputedVisibilityCells[0].Bounds.Min.X, CombinedPrecomputedVisibilityCells[0].Bounds.Min.Y); TArray > CellRenderingBuckets; CellRenderingBuckets.Empty(NumCellBuckets * NumCellBuckets); CellRenderingBuckets.AddZeroed(NumCellBuckets * NumCellBuckets); SIZE_T UncompressedSize = 0; // Sort the cells into buckets based on their position //@todo - sort cells inside buckets based on locality, to reduce visibility cache misses for (int32 CellIndex = 0; CellIndex < CombinedPrecomputedVisibilityCells.Num(); CellIndex++) { const FUncompressedPrecomputedVisibilityCell& CurrentCell = CombinedPrecomputedVisibilityCells[CellIndex]; const float FloatOffsetX = (CurrentCell.Bounds.Min.X - CellBucketOriginXY.X + .5f * CellSize) / CellSize; // FMath::TruncToInt rounds toward 0, we want to always round down const int32 BucketIndexX = FMath::Abs((FMath::TruncToInt(FloatOffsetX) - (FloatOffsetX < 0.0f ? 1 : 0)) / CellBucketSize % NumCellBuckets); const float FloatOffsetY = (CurrentCell.Bounds.Min.Y - CellBucketOriginXY.Y + .5f * CellSize) / CellSize; const int32 BucketIndexY = FMath::Abs((FMath::TruncToInt(FloatOffsetY) - (FloatOffsetY < 0.0f ? 1 : 0)) / CellBucketSize % NumCellBuckets); const int32 BucketIndex = BucketIndexY * CellBucketSize + BucketIndexX; CellRenderingBuckets[BucketIndex].Add(&CurrentCell); UncompressedSize += CurrentCell.VisibilityData.Num(); } System.GetWorld()->PersistentLevel->MarkPackageDirty(); // Set all the level parameters needed to access visibility System.GetWorld()->PersistentLevel->PrecomputedVisibilityHandler.PrecomputedVisibilityCellBucketOriginXY = CellBucketOriginXY; System.GetWorld()->PersistentLevel->PrecomputedVisibilityHandler.PrecomputedVisibilityCellSizeXY = CellSize; System.GetWorld()->PersistentLevel->PrecomputedVisibilityHandler.PrecomputedVisibilityCellSizeZ = PlayAreaHeight; System.GetWorld()->PersistentLevel->PrecomputedVisibilityHandler.PrecomputedVisibilityCellBucketSizeXY = CellBucketSize; System.GetWorld()->PersistentLevel->PrecomputedVisibilityHandler.PrecomputedVisibilityNumCellBuckets = NumCellBuckets; System.GetWorld()->PersistentLevel->PrecomputedVisibilityHandler.PrecomputedVisibilityCellBuckets.Empty(NumCellBuckets * NumCellBuckets); System.GetWorld()->PersistentLevel->PrecomputedVisibilityHandler.PrecomputedVisibilityCellBuckets.AddZeroed(NumCellBuckets * NumCellBuckets); // Split visibility data into ~32Kb chunks, to limit decompression time const int32 ChunkSizeTarget = 32 * 1024; TArray UncompressedVisibilityData; SIZE_T TotalCompressedSize = 0; for (int32 BucketIndex = 0; BucketIndex < CellRenderingBuckets.Num(); BucketIndex++) { FPrecomputedVisibilityBucket& OutputBucket = System.GetWorld()->PersistentLevel->PrecomputedVisibilityHandler.PrecomputedVisibilityCellBuckets[BucketIndex]; OutputBucket.CellDataSize = CombinedPrecomputedVisibilityCells[0].VisibilityData.Num(); int32 ChunkIndex = 0; UncompressedVisibilityData.Reset(); for (int32 CellIndex = 0; CellIndex < CellRenderingBuckets[BucketIndex].Num(); CellIndex++) { const FUncompressedPrecomputedVisibilityCell& CurrentCell = *CellRenderingBuckets[BucketIndex][CellIndex]; FPrecomputedVisibilityCell NewCell; NewCell.Min = CurrentCell.Bounds.Min; // We're only storing Min per cell with a shared SizeXY and SizeZ for reduced memory storage checkSlow(CurrentCell.Bounds.Max.Equals(CurrentCell.Bounds.Min + FVector(CellSize, CellSize, PlayAreaHeight), KINDA_SMALL_NUMBER * 10.0f)); NewCell.ChunkIndex = ChunkIndex; NewCell.DataOffset = UncompressedVisibilityData.Num(); OutputBucket.Cells.Add(NewCell); UncompressedVisibilityData.Append(CurrentCell.VisibilityData); // Create a new chunk if we've reached the size limit or this is the last cell in a bucket if (UncompressedVisibilityData.Num() > ChunkSizeTarget || CellIndex == CellRenderingBuckets[BucketIndex].Num() - 1) { // Don't compress small amounts of data because FCompression::CompressMemory will fail if (bCompressVisibilityData && UncompressedVisibilityData.Num() > 32) { TArray TempCompressionOutput; // Compressed output can be larger than the input, so we use temporary storage to hold the compressed output for now TempCompressionOutput.Empty(UncompressedVisibilityData.Num() * 4 / 3); TempCompressionOutput.AddUninitialized(UncompressedVisibilityData.Num() * 4 / 3); int32 CompressedSize = TempCompressionOutput.Num(); verify(FCompression::CompressMemory( // Using zlib since it is supported on all platforms, otherwise we would need to compress on cook NAME_Zlib, TempCompressionOutput.GetData(), CompressedSize, UncompressedVisibilityData.GetData(), UncompressedVisibilityData.Num(), COMPRESS_BiasMemory)); OutputBucket.CellDataChunks.AddZeroed(); FCompressedVisibilityChunk& NewChunk = OutputBucket.CellDataChunks.Last(); NewChunk.UncompressedSize = UncompressedVisibilityData.Num(); NewChunk.bCompressed = true; NewChunk.Data.Empty(CompressedSize); NewChunk.Data.AddUninitialized(CompressedSize); FMemory::Memcpy(NewChunk.Data.GetData(), TempCompressionOutput.GetData(), CompressedSize); ChunkIndex++; TotalCompressedSize += CompressedSize; UncompressedVisibilityData.Reset(); } else { OutputBucket.CellDataChunks.AddZeroed(); FCompressedVisibilityChunk& NewChunk = OutputBucket.CellDataChunks.Last(); NewChunk.UncompressedSize = UncompressedVisibilityData.Num(); NewChunk.bCompressed = false; NewChunk.Data = UncompressedVisibilityData; ChunkIndex++; TotalCompressedSize += UncompressedVisibilityData.Num(); UncompressedVisibilityData.Reset(); } } } } System.GetWorld()->PersistentLevel->PrecomputedVisibilityHandler.UpdateVisibilityStats(true); UE_LOG(LogStaticLightingSystem, Log, TEXT("ApplyPrecomputedVisibility %.1fs with %u cells, %.1f%% of all queries changed to visible from spreading neighbors, compressed %.3fMb to %.3fMb (%.1f ratio)"), FPlatformTime::Seconds() - StartTime, CombinedPrecomputedVisibilityCells.Num(), 100.0f * QueriesVisibleFromSpreadingNeighbors / TotalNumQueries, UncompressedSize / 1024.0f / 1024.0f, TotalCompressedSize / 1024.0f / 1024.0f, UncompressedSize / (float)TotalCompressedSize); } else { System.GetWorld()->PersistentLevel->PrecomputedVisibilityHandler.Invalidate(System.GetWorld()->Scene); } } /** Imports data from Lightmass about the mesh area lights generated for the scene, and creates AGeneratedMeshAreaLight's for them. */ void FLightmassProcessor::ImportMeshAreaLightData() { if (MeshAreaLightDataTaskCompleted > 0) { const FString ChannelName = Lightmass::CreateChannelName(Lightmass::MeshAreaLightDataGuid, Lightmass::LM_MESHAREALIGHTDATA_VERSION, Lightmass::LM_MESHAREALIGHTDATA_EXTENSION); const int32 Channel = Swarm.OpenChannel( *ChannelName, LM_MESHAREALIGHT_CHANNEL_FLAGS ); if (Channel >= 0) { int32 NumMeshAreaLights = 0; Swarm.ReadChannel(Channel, &NumMeshAreaLights, sizeof(NumMeshAreaLights)); for (int32 LightIndex = 0; LightIndex < NumMeshAreaLights; LightIndex++) { Lightmass::FMeshAreaLightData LMCurrentLightData; Swarm.ReadChannel(Channel, &LMCurrentLightData, sizeof(LMCurrentLightData)); const ULevel* CurrentLevel = System.LightingContext.GetLevelForGuid(LMCurrentLightData.LevelGuid).Get(); if (CurrentLevel && CurrentLevel->Actors.Num() > 0) { // Find the level that the mesh area light was in FVector4 Position; Position = (FVector4)LMCurrentLightData.Position; FVector4 Direction; Direction = (FVector4)LMCurrentLightData.Direction; // Spawn a AGeneratedMeshAreaLight to handle the light's influence on dynamic objects FActorSpawnParameters SpawnInfo; SpawnInfo.Owner = CurrentLevel->GetWorldSettings(); AGeneratedMeshAreaLight* NewGeneratedLight = CurrentLevel->OwningWorld->SpawnActor(Position, Direction.Rotation()); USpotLightComponent* SpotComponent = CastChecked(NewGeneratedLight->GetLightComponent()); // Unregister the component before we change its attributes FComponentReregisterContext Reregister(SpotComponent); // Setup spotlight properties to approximate a mesh area light SpotComponent->AttenuationRadius = LMCurrentLightData.Radius; SpotComponent->OuterConeAngle = LMCurrentLightData.ConeAngle * 180.0f / PI; SpotComponent->LightColor = LMCurrentLightData.Color; SpotComponent->Intensity = LMCurrentLightData.Brightness; SpotComponent->LightFalloffExponent = LMCurrentLightData.FalloffExponent; } } Swarm.CloseChannel(Channel); } else { UE_LOG(LogLightmassSolver, Log, TEXT("Error, OpenChannel failed to open %s with error code %d"), *ChannelName, Channel ); } FPlatformAtomics::InterlockedExchange(&MeshAreaLightDataTaskCompleted, 0); } } /** Imports the volume distance field from Lightmass. */ void FLightmassProcessor::ImportVolumeDistanceFieldData() { if (VolumeDistanceFieldTaskCompleted > 0) { const FString ChannelName = Lightmass::CreateChannelName(Lightmass::VolumeDistanceFieldGuid, Lightmass::LM_MESHAREALIGHTDATA_VERSION, Lightmass::LM_MESHAREALIGHTDATA_EXTENSION); const int32 Channel = Swarm.OpenChannel( *ChannelName, LM_MESHAREALIGHT_CHANNEL_FLAGS ); if (Channel >= 0) { FPrecomputedVolumeDistanceField& DistanceField = System.GetWorld()->PersistentLevel->PrecomputedVolumeDistanceField; Swarm.ReadChannel(Channel, &DistanceField.VolumeSizeX, sizeof(DistanceField.VolumeSizeX)); Swarm.ReadChannel(Channel, &DistanceField.VolumeSizeY, sizeof(DistanceField.VolumeSizeY)); Swarm.ReadChannel(Channel, &DistanceField.VolumeSizeZ, sizeof(DistanceField.VolumeSizeZ)); Swarm.ReadChannel(Channel, &DistanceField.VolumeMaxDistance, sizeof(DistanceField.VolumeMaxDistance)); FVector4 BoxMin; Swarm.ReadChannel(Channel, &BoxMin, sizeof(BoxMin)); FVector4 BoxMax; Swarm.ReadChannel(Channel, &BoxMax, sizeof(BoxMax)); DistanceField.VolumeBox = FBox(BoxMin, BoxMax); ReadArray(Channel, DistanceField.Data); Swarm.CloseChannel(Channel); } else { UE_LOG(LogLightmassSolver, Log, TEXT("Error, OpenChannel failed to open %s with error code %d"), *ChannelName, Channel ); } FPlatformAtomics::InterlockedExchange(&VolumeDistanceFieldTaskCompleted, 0); } } /** * Import the texture mapping * @param TextureMapping The mapping being imported. * @param bProcessImmediately If true, immediately process the mapping * If false, store it off for later processing */ void FLightmassProcessor::ImportStaticLightingTextureMapping( const FGuid& MappingGuid, bool bProcessImmediately ) { FString ChannelName = Lightmass::CreateChannelName(MappingGuid, Lightmass::LM_TEXTUREMAPPING_VERSION, Lightmass::LM_TEXTUREMAPPING_EXTENSION); // We need to check if there's a channel with this name for each completed mapping, // even if the mapping has been imported as part of a previous channel. // Example: // 1. If the remote agent gets reassigned, it might have written out a merged channel // (mappings A, B, C and D in one channel) but only sent out a "completed" message for // some of the mappings (e.g. A and B). // 2. Unreal imports A, B, C and D when it receives the "completed" message for A. // 3. A new remote agent will process C, D and some new mappings E and F, and write out // a merged channel named "C", containing C, D, E, F. // 4. Unreal must now read the "C" channel - even if C has been imported already - in order // to import E and F. int32 Channel = Swarm.OpenChannel( *ChannelName, LM_TEXTUREMAPPING_CHANNEL_FLAGS ); if (Channel >= 0) { // Read in how many mappings this channel contains uint32 MappingsImported = 0; uint32 NumMappings = 0; Swarm.ReadChannel(Channel, &NumMappings, sizeof(NumMappings)); // Read in each of the mappings while (MappingsImported != NumMappings) { // Read in the next GUID and look up its mapping FGuid NextMappingGuid; Swarm.ReadChannel(Channel, &NextMappingGuid, sizeof(FGuid)); FStaticLightingTextureMapping* TextureMapping = GetStaticLightingTextureMapping(NextMappingGuid); if (TextureMapping && !TextureMapping->IsValidMapping()) { // Mapping is invalid (such as in the case of BSP being invalidated), discard the rest of the file break; } // If we don't have a mapping pending, check to see if we've already imported // it which can *possibly* happen if a disconnection race condition occured // where we got the results for a task, but didn't get the message that it // had finished before we re-queued/re-assigned the task to another agent, // which could result in duplicate results. If we get a duplicate, just // re-import the redundant results. bool bReimporting = false; if ( TextureMapping == NULL ) { // Remove the mapping from ImportedMappings and re-import it. FMappingImportHelper** pImportData = ImportedMappings.Find( NextMappingGuid ); check( pImportData && *pImportData && (*pImportData)->Type == SLT_Texture ); FTextureMappingImportHelper* pTextureImportData = (*pImportData)->GetTextureMappingHelper(); TextureMapping = pTextureImportData->TextureMapping.GetReference(); bReimporting = true; if ( GLightmassStatsMode ) { UE_LOG(LogLightmassSolver, Log, TEXT("Re-importing texture mapping: %s"), *NextMappingGuid.ToString()); } } if( ensureMsgf( (TextureMapping != NULL), TEXT("Opened mapping channel %s to Swarm, then tried to find texture mapping %s (number %d of %d) and failed."), *MappingGuid.ToString(), *NextMappingGuid.ToString(), MappingsImported, NumMappings) ) { //UE_LOG(LogLightmassSolver, Log, TEXT("Importing %32s %s"), *(TextureMapping->GetDescription()), *(TextureMapping->GetLightingGuid().ToString())); FTextureMappingImportHelper* ImportData = new FTextureMappingImportHelper(); ImportData->TextureMapping = TextureMapping; ImportData->MappingGuid = NextMappingGuid; if (ImportTextureMapping(Channel, *ImportData) == true) { if ( bReimporting == false ) { ImportedMappings.Add(ImportData->MappingGuid, ImportData); } if (bProcessImmediately) { ProcessMapping(ImportData->MappingGuid); } } else { UE_LOG(LogLightmassSolver, Warning, TEXT("Failed to import TEXTure mapping results!")); } // Completed this mapping, increment MappingsImported++; } else { // Report an error for this mapping UObject* Object = NULL; const FStaticLightingMapping* FoundMapping = Exporter->FindMappingByGuid(NextMappingGuid); if (FoundMapping) { Object = FoundMapping->GetMappedObject(); } FMessageLog("LightingResults").Error() ->AddToken(FUObjectToken::Create(Object)) ->AddToken(FTextToken::Create(LOCTEXT("LightmassError_LightingBuildError", "Lighting build error"))); // We can't trust the rest of this file, so we'll need to bail now break; } } Swarm.CloseChannel( Channel ); } // File not found? else if ( Channel == NSwarm::SWARM_ERROR_CHANNEL_NOT_FOUND ) { // If the channel doesn't exist, then this mapping could've been part of another channel // that has already been imported, so attempt to remove the mapping FStaticLightingTextureMapping* TextureMapping = GetStaticLightingTextureMapping(MappingGuid); // Alternatively, this channel could be part of an invalidated mapping } // Other error else { UE_LOG(LogLightmassSolver, Log, TEXT("Error, OpenChannel failed to open %s with error code %d"), *ChannelName, Channel ); } } /** Determines whether the specified mapping is a texture mapping */ bool FLightmassProcessor::IsStaticLightingTextureMapping( const FGuid& MappingGuid ) { if (PendingBSPMappings.Contains(MappingGuid)) { return true; } else if (PendingTextureMappings.Contains(MappingGuid)) { return true; } else if (PendingLandscapeMappings.Contains(MappingGuid)) { return true; } else { FMappingImportHelper** pImportData = ImportedMappings.Find(MappingGuid); if ( pImportData && (*pImportData)->Type == SLT_Texture ) { return true; } } return false; } /** Gets the texture mapping for the specified GUID */ FStaticLightingTextureMapping* FLightmassProcessor::GetStaticLightingTextureMapping( const FGuid& MappingGuid ) { FBSPSurfaceStaticLighting* BSPMapping = NULL; FStaticMeshStaticLightingTextureMapping* SMTextureMapping = NULL; FLandscapeStaticLightingTextureMapping* LandscapeMapping = NULL; if (PendingBSPMappings.RemoveAndCopyValue(MappingGuid, BSPMapping)) { return BSPMapping->GetTextureMapping(); } else if (PendingTextureMappings.RemoveAndCopyValue(MappingGuid, SMTextureMapping)) { return SMTextureMapping->GetTextureMapping(); } else if (PendingLandscapeMappings.RemoveAndCopyValue(MappingGuid, LandscapeMapping)) { return LandscapeMapping->GetTextureMapping(); } return NULL; } void FLightmassProcessor::ImportStaticShadowDepthMap(ULightComponent* Light) { const FString ChannelName = Lightmass::CreateChannelName(Light->LightGuid, Lightmass::LM_DOMINANTSHADOW_VERSION, Lightmass::LM_DOMINANTSHADOW_EXTENSION); const int32 Channel = Swarm.OpenChannel( *ChannelName, LM_DOMINANTSHADOW_CHANNEL_FLAGS ); if (Channel >= 0) { UMapBuildDataRegistry* CurrentRegistry = System.LightingContext.GetOrCreateRegistryForActor(Light->GetOwner()); FLightComponentMapBuildData& CurrentLightData = CurrentRegistry->FindOrAllocateLightBuildData(Light->LightGuid, true); Lightmass::FStaticShadowDepthMapData ShadowMapData; Swarm.ReadChannel(Channel, &ShadowMapData, sizeof(ShadowMapData)); BeginReleaseResource(&Light->StaticShadowDepthMap); CurrentLightData.DepthMap.Empty(); CurrentLightData.DepthMap.WorldToLight = FMatrix(ShadowMapData.WorldToLight); CurrentLightData.DepthMap.ShadowMapSizeX = ShadowMapData.ShadowMapSizeX; CurrentLightData.DepthMap.ShadowMapSizeY = ShadowMapData.ShadowMapSizeY; ReadArray(Channel, CurrentLightData.DepthMap.DepthSamples); Swarm.CloseChannel(Channel); CurrentLightData.FinalizeLoad(); } else { UE_LOG(LogLightmassSolver, Log, TEXT("Error, OpenChannel failed to open %s with error code %d"), *ChannelName, Channel ); } } /** * Import the mapping specified by a Guid. * @param MappingGuid Guid that identifies a mapping * @param bProcessImmediately If true, immediately process the mapping * If false, store it off for later processing */ void FLightmassProcessor::ImportMapping( const FGuid& MappingGuid, bool bProcessImmediately ) { double ImportAndApplyStartTime = FPlatformTime::Seconds(); double OriginalApplyTime = Statistics.ApplyTimeInProcessing; if (IsStaticLightingTextureMapping(MappingGuid)) { ImportStaticLightingTextureMapping(MappingGuid, bProcessImmediately); } else { ULightComponent* Light = FindLight(MappingGuid); if (Light) { ImportStaticShadowDepthMap(Light); } else { FMappingImportHelper** pImportData = ImportedMappings.Find(MappingGuid); if ((pImportData == NULL) || (*pImportData == NULL)) { UE_LOG(LogLightmassSolver, Warning, TEXT("Mapping not found for %s"), *(MappingGuid.ToString())); } } } if ( !bRunningLightmass ) { double ApplyTime = Statistics.ApplyTimeInProcessing - OriginalApplyTime; double ImportTime = FPlatformTime::Seconds() - ImportAndApplyStartTime - ApplyTime; Statistics.ImportTimeInProcessing += ImportTime; } } /** * Process the mapping specified by a Guid. * @param MappingGuid Guid that identifies a mapping **/ void FLightmassProcessor::ProcessMapping( const FGuid& MappingGuid ) { double ApplyStartTime = FPlatformTime::Seconds(); FMappingImportHelper** pImportData = ImportedMappings.Find(MappingGuid); if ( pImportData && *pImportData ) { if ( (*pImportData)->bProcessed == false ) { FMappingImportHelper* ImportData = *pImportData; switch (ImportData->Type) { case SLT_Texture: { FTextureMappingImportHelper* TImportData = (FTextureMappingImportHelper*)ImportData; if(TImportData->TextureMapping) { //UE_LOG(LogLightmassSolver, Log, TEXT("Processing %32s: %s"), *(TImportData->TextureMapping->GetDescription()), *(TImportData->TextureMapping->GetLightingGuid().ToString())); System.ApplyMapping(TImportData->TextureMapping, TImportData->QuantizedData, TImportData->ShadowMapData); } else { //UE_LOG(LogLightmassSolver, Log, TEXT("Processing texture mapping %s failed due to missing mapping!"), *MappingGuid.ToString()); } } break; default: { UE_LOG(LogLightmassSolver, Warning, TEXT("Unknown mapping type in the ImportedMappings: 0x%08x"), (uint32)(ImportData->Type)); } } (*pImportData)->bProcessed = true; } else { // Just to be able to set a breakpoint here. int32 DebugDummy = 0; } } else { UE_LOG(LogLightmassSolver, Warning, TEXT("Failed to find imported mapping %s"), *(MappingGuid.ToString())); } if ( !bRunningLightmass ) { Statistics.ApplyTimeInProcessing += FPlatformTime::Seconds() - ApplyStartTime; } } /** * Process any available mappings. **/ void FLightmassProcessor::ProcessAvailableMappings() { bool bDoneProcessing = false; int32 ProcessedCount = 0; int32 ImportedMappingsCount = ImportedMappings.Num(); while (!bDoneProcessing) { FGuid NextGuid = FGuid(0,0,0,MappingToProcessIndex); FMappingImportHelper** pImportData = ImportedMappings.Find(NextGuid); if (pImportData) { if ( *pImportData && (*pImportData)->bProcessed == false ) { if (IsDeferredMapping(NextGuid)) { DeferMapping((*pImportData)->GetTextureMappingHelper()); } else { ProcessMapping(NextGuid); } } ProcessedCount++; } MappingToProcessIndex++; if (ProcessedCount >= ImportedMappingsCount) { bDoneProcessing = true; } } } void FLightmassProcessor::ImportDebugOutputStruct(int32 Channel) { static_assert(sizeof(FDebugStaticLightingRay) == sizeof(Lightmass::FDebugStaticLightingRay), "Debug type sizes must match for FDebugStaticLightingRay."); static_assert(sizeof(FDebugStaticLightingVertex) == sizeof(Lightmass::FDebugStaticLightingVertex), "Debug type sizes must match for FDebugStaticLightingVertex."); static_assert(sizeof(FDebugLightingCacheRecord) == sizeof(Lightmass::FDebugLightingCacheRecord), "Debug type sizes must match for FDebugLightingCacheRecord."); static_assert(STRUCT_OFFSET(FDebugLightingCacheRecord, RecordId) == STRUCT_OFFSET(Lightmass::FDebugLightingCacheRecord, RecordId), "Debug struct offset must match for FDebugLightingCacheRecord::RecordId."); static_assert(sizeof(FDebugPhoton) == sizeof(Lightmass::FDebugPhoton), "Debug type sizes must match for FDebugPhoton."); static_assert(sizeof(FDebugOctreeNode) == sizeof(Lightmass::FDebugOctreeNode), "Debug type sizes must match for FDebugOctreeNode."); static_assert(NumTexelCorners == Lightmass::NumTexelCorners, "Debug type sizes must match for NumTexelCorners."); bool bDebugInfoValid = false; Swarm.ReadChannel(Channel, &bDebugInfoValid, sizeof(bDebugInfoValid)); if (bDebugInfoValid) { if (GDebugStaticLightingInfo.bValid) { UE_LOG(LogLightmassSolver, Log, TEXT("Error, importing valid debug info, but GDebugStaticLightingInfo was already valid (multiple sources of debug info from Lightmass)")); } GDebugStaticLightingInfo.bValid = true; ReadArray(Channel, GDebugStaticLightingInfo.PathRays); ReadArray(Channel, GDebugStaticLightingInfo.ShadowRays); ReadArray(Channel, GDebugStaticLightingInfo.IndirectPhotonPaths); ReadArray(Channel, GDebugStaticLightingInfo.SelectedVertexIndices); ReadArray(Channel, GDebugStaticLightingInfo.Vertices); ReadArray(Channel, GDebugStaticLightingInfo.CacheRecords); ReadArray(Channel, GDebugStaticLightingInfo.DirectPhotons); ReadArray(Channel, GDebugStaticLightingInfo.IndirectPhotons); ReadArray(Channel, GDebugStaticLightingInfo.IrradiancePhotons); ReadArray(Channel, GDebugStaticLightingInfo.GatheredPhotons); ReadArray(Channel, GDebugStaticLightingInfo.GatheredImportancePhotons); ReadArray(Channel, GDebugStaticLightingInfo.GatheredPhotonNodes); Swarm.ReadChannel(Channel, &GDebugStaticLightingInfo.bDirectPhotonValid, sizeof(GDebugStaticLightingInfo.bDirectPhotonValid)); Swarm.ReadChannel(Channel, &GDebugStaticLightingInfo.GatheredDirectPhoton, sizeof(GDebugStaticLightingInfo.GatheredDirectPhoton)); Swarm.ReadChannel(Channel, &GDebugStaticLightingInfo.TexelCorners, sizeof(GDebugStaticLightingInfo.TexelCorners)); Swarm.ReadChannel(Channel, &GDebugStaticLightingInfo.bCornerValid, sizeof(GDebugStaticLightingInfo.bCornerValid)); Swarm.ReadChannel(Channel, &GDebugStaticLightingInfo.SampleRadius, sizeof(GDebugStaticLightingInfo.SampleRadius)); } } /** * Retrieve the light for the given Guid * * @param LightGuid The guid of the light we are looking for * * @return ULightComponent* The corresponding light component. * NULL if not found. */ ULightComponent* FLightmassProcessor::FindLight(const FGuid& LightGuid) { if (Exporter) { int32 LightIndex; for (LightIndex = 0; LightIndex < Exporter->DirectionalLights.Num(); LightIndex++) { const UDirectionalLightComponent* Light = Exporter->DirectionalLights[LightIndex]; if (Light) { if (Light->LightGuid == LightGuid) { return (ULightComponent*)Light; } } } for (LightIndex = 0; LightIndex < Exporter->PointLights.Num(); LightIndex++) { const UPointLightComponent* Light = Exporter->PointLights[LightIndex]; if (Light) { if (Light->LightGuid == LightGuid) { return (ULightComponent*)Light; } } } for (LightIndex = 0; LightIndex < Exporter->SpotLights.Num(); LightIndex++) { const USpotLightComponent* Light = Exporter->SpotLights[LightIndex]; if (Light) { if (Light->LightGuid == LightGuid) { return (ULightComponent*)Light; } } } for (LightIndex = 0; LightIndex < Exporter->RectLights.Num(); LightIndex++) { const URectLightComponent* Light = Exporter->RectLights[LightIndex]; if (Light) { if (Light->LightGuid == LightGuid) { return (ULightComponent*)Light; } } } } return NULL; } /** * Retrieve the static mehs for the given Guid * * @param Guid The guid of the static mesh we are looking for * * @return UStaticMesh* The corresponding static mesh. * NULL if not found. */ UStaticMesh* FLightmassProcessor::FindStaticMesh(FGuid& Guid) { if (Exporter) { for (int32 SMIdx = 0; SMIdx < Exporter->StaticMeshes.Num(); SMIdx++) { const UStaticMesh* StaticMesh = Exporter->StaticMeshes[SMIdx]; if (StaticMesh && (StaticMesh->GetLightingGuid() == Guid)) { return (UStaticMesh*)StaticMesh; } } } return NULL; } ULevel* FLightmassProcessor::FindLevel(const FGuid& Guid) { if (Exporter) { const TWeakObjectPtr Level = Exporter->LightingContext.GetLevelForGuid(Guid); return Level.IsValid() ? Level.Get() : NULL; } return NULL; } /** * Import light map data from the given channel. * * @param Channel The channel to import from. * @param QuantizedData The quantized lightmap data to fill in * @param UncompressedSize Size the data will be after uncompressing it (if compressed) * @param CompressedSize Size of the source data if compressed * * @return bool true if successful, false otherwise. */ bool FLightmassProcessor::ImportLightMapData2DData(int32 Channel, FQuantizedLightmapData* QuantizedData, int32 UncompressedSize, int32 CompressedSize) { check(QuantizedData); int32 SizeX = QuantizedData->SizeX; int32 SizeY = QuantizedData->SizeY; // make space for the samples QuantizedData->Data.Empty(SizeX * SizeY); QuantizedData->Data.AddUninitialized(SizeX * SizeY); FLightMapCoefficients* DataBuffer = QuantizedData->Data.GetData(); int32 DataBufferSize = SizeX * SizeY * sizeof(FLightMapCoefficients); check(DataBufferSize == UncompressedSize); // read in the compressed data void* CompressedBuffer = FMemory::Malloc(CompressedSize); Swarm.ReadChannel(Channel, CompressedBuffer, CompressedSize); // decompress the temp buffer into another temp buffer if (!FCompression::UncompressMemory(NAME_Zlib, DataBuffer, UncompressedSize, CompressedBuffer, CompressedSize)) { checkf(false, TEXT("Uncompress failed, which is unexpected")); } // can free one buffer now FMemory::Free(CompressedBuffer); return true; } bool FLightmassProcessor::ImportSignedDistanceFieldShadowMapData2D(int32 Channel, TMap& OutShadowMapData, int32 ShadowMapCount) { for (int32 SMIndex = 0; SMIndex < ShadowMapCount; SMIndex++) { FGuid LightGuid; Swarm.ReadChannel(Channel, &LightGuid, sizeof(FGuid)); ULightComponent* LightComp = FindLight(LightGuid); if (LightComp == NULL) { UE_LOG(LogLightmassSolver, Warning, TEXT("Failed to find light for texture mapping: %s"), *LightGuid.ToString()); } Lightmass::FShadowMapData2DData SMData(0,0); Swarm.ReadChannel(Channel, &SMData, sizeof(Lightmass::FShadowMapData2DData)); static_assert(sizeof(FQuantizedSignedDistanceFieldShadowSample) == sizeof(Lightmass::FQuantizedSignedDistanceFieldShadowSampleData), "Sample data sizes must match."); FQuantizedShadowSignedDistanceFieldData2D* ShadowMapData = new FQuantizedShadowSignedDistanceFieldData2D(SMData.SizeX, SMData.SizeY); check(ShadowMapData); FQuantizedSignedDistanceFieldShadowSample* DataBuffer = ShadowMapData->GetData(); uint32 DataBufferSize = SMData.SizeX * SMData.SizeY * sizeof(Lightmass::FQuantizedSignedDistanceFieldShadowSampleData); uint32 CompressedSize = SMData.CompressedDataSize; uint32 UncompressedSize = SMData.UncompressedDataSize; check(DataBufferSize == UncompressedSize); // Read in the compressed data void* CompressedBuffer = FMemory::Malloc(CompressedSize); Swarm.ReadChannel(Channel, CompressedBuffer, CompressedSize); // Decompress the temp buffer into another temp buffer if (!FCompression::UncompressMemory(NAME_Zlib, DataBuffer, UncompressedSize, CompressedBuffer, CompressedSize)) { checkf(false, TEXT("Uncompress failed, which is unexpected")); } FMemory::Free(CompressedBuffer); if (LightComp) { OutShadowMapData.Add(LightComp, ShadowMapData); } else { delete ShadowMapData; } } return true; } /** * Import a complete texture mapping.... * * @param Channel The channel to import from. * @param TMImport The texture mapping information that will be imported. * * @return bool true if successful, false otherwise. */ bool FLightmassProcessor::ImportTextureMapping(int32 Channel, FTextureMappingImportHelper& TMImport) { bool bResult = true; // Additional information for this mapping Swarm.ReadChannel(Channel, &(TMImport.ExecutionTime), sizeof(double)); // The resulting light map data for this mapping (shared header and TArray) Lightmass::FLightMapData2DData LMLightmapData2DData(0,0); Swarm.ReadChannel(Channel, &LMLightmapData2DData, sizeof(Lightmass::FLightMapData2DData)); check(TMImport.TextureMapping->SizeX == LMLightmapData2DData.SizeX); check(TMImport.TextureMapping->SizeY == LMLightmapData2DData.SizeY); Swarm.ReadChannel(Channel, &TMImport.NumShadowMaps, sizeof(TMImport.NumShadowMaps)); Swarm.ReadChannel(Channel, &TMImport.NumSignedDistanceFieldShadowMaps, sizeof(TMImport.NumSignedDistanceFieldShadowMaps)); int32 NumLights = 0; TArray LightGuids; LightGuids.Empty(NumLights); Swarm.ReadChannel(Channel, &NumLights, sizeof(NumLights)); for (int32 i = 0; i < NumLights; i++) { const int32 NewLightIndex = LightGuids.AddUninitialized(); Swarm.ReadChannel(Channel, &LightGuids[NewLightIndex], sizeof(LightGuids[NewLightIndex])); } // allocate space to store the quantized data TMImport.QuantizedData = new FQuantizedLightmapData; FMemory::Memcpy(TMImport.QuantizedData->Scale, LMLightmapData2DData.Multiply, sizeof(TMImport.QuantizedData->Scale)); FMemory::Memcpy(TMImport.QuantizedData->Add, LMLightmapData2DData.Add, sizeof(TMImport.QuantizedData->Add)); TMImport.QuantizedData->SizeX = LMLightmapData2DData.SizeX; TMImport.QuantizedData->SizeY = LMLightmapData2DData.SizeY; TMImport.QuantizedData->LightGuids = LightGuids; TMImport.QuantizedData->bHasSkyShadowing = LMLightmapData2DData.bHasSkyShadowing; if (ImportLightMapData2DData(Channel, TMImport.QuantizedData, LMLightmapData2DData.UncompressedDataSize, LMLightmapData2DData.CompressedDataSize) == false) { bResult = false; } int32 NumUnmappedTexels = 0; for (int32 DebugIdx = 0; DebugIdx < TMImport.QuantizedData->Data.Num(); DebugIdx++) { if (TMImport.QuantizedData->Data[DebugIdx].Coverage == 0.0f) { NumUnmappedTexels++; } } if (TMImport.QuantizedData->Data.Num() > 0) { TMImport.UnmappedTexelsPercentage = 100.0f * (float)NumUnmappedTexels / (float)TMImport.QuantizedData->Data.Num(); } else { TMImport.UnmappedTexelsPercentage = 0.0f; } if (ImportSignedDistanceFieldShadowMapData2D(Channel, TMImport.ShadowMapData, TMImport.NumSignedDistanceFieldShadowMaps) == false) { bResult = false; } ImportDebugOutputStruct(Channel); // Update the LightingBuildInfo list UObject* MappedObject = TMImport.TextureMapping->GetMappedObject(); float MemoryAmount = float(NumUnmappedTexels); float TotalMemoryAmount = float(TMImport.QuantizedData->Data.Num()); //@todo. Move this into some common place... it's defined in several places now! const float MIP_FACTOR = 4.0f / 3.0f; // Assume compressed == 4 bits/pixel, really this is platform specific. float BytesPerPixel = 1.0f; float LightMapTypeModifier = NUM_HQ_LIGHTMAP_COEF; if (!AllowHighQualityLightmaps(GMaxRHIFeatureLevel)) { LightMapTypeModifier = NUM_LQ_LIGHTMAP_COEF; } int32 WastedMemory = FMath::TruncToInt(MemoryAmount * BytesPerPixel * MIP_FACTOR * LightMapTypeModifier); int32 TotalMemory = FMath::TruncToInt(TotalMemoryAmount * BytesPerPixel * MIP_FACTOR * LightMapTypeModifier); FStatsViewerModule& StatsViewerModule = FModuleManager::Get().LoadModuleChecked(TEXT("StatsViewer")); ULightingBuildInfo* LightingBuildInfo = NewObject(); LightingBuildInfo->Set( MappedObject, TMImport.ExecutionTime, TMImport.UnmappedTexelsPercentage, WastedMemory, TotalMemory ); StatsViewerModule.GetPage(EStatsPage::LightingBuildInfo)->AddEntry( LightingBuildInfo ); return bResult; } void FLightmassProcessor::FMappingImportHelper::Serialize(FArchive& Ar) { static_assert(sizeof(Type) == sizeof(int32)); Ar << (int32&)Type; Ar << MappingGuid; Ar << OwnerGuid; Ar << ExecutionTime; Ar << bProcessed; } void FLightmassProcessor::FTextureMappingImportHelper::Serialize(FArchive& Ar) { FMappingImportHelper::Serialize(Ar); if (Ar.IsLoading()) { int32 MappingType; Ar << MappingType; switch (MappingType) { case 0: TextureMapping = new FStaticMeshStaticLightingTextureMapping(Ar); break; case 1: TextureMapping = new FLandscapeStaticLightingTextureMapping(Ar); break; case 2: TextureMapping = new FStaticLightingTextureMapping_InstancedStaticMesh(Ar); } // Ptr will be deleted during FStaticLightingTextureMapping::Apply QuantizedData = new FQuantizedLightmapData; } else { static TMap DescToType = {{"SMTextureMapping", 0}, { "LandscapeMapping", 1 }, {"InstancedSMLightingMapping", 2}}; int32 mappingType = DescToType.FindChecked(TextureMapping->GetDescription()); Ar << mappingType; } TextureMapping->Serialize(Ar); QuantizedData->Serialize(Ar); Ar << UnmappedTexelsPercentage; Ar << NumShadowMaps; Ar << NumSignedDistanceFieldShadowMaps; int32 NbShadowMapData = ShadowMapData.Num(); Ar << NbShadowMapData; if (Ar.IsLoading()) { for (int32 i = 0; i < NbShadowMapData; i++) { FSoftObjectPath Path; Ar << Path; int32 ShadowMapType; Ar << ShadowMapType; int32 SizeX; int32 SizeY; Ar << SizeX; Ar << SizeY; // Ptr will be deleted during FStaticLightingTextureMapping::Apply FShadowMapData2D* Data = nullptr; switch ((FShadowMapData2D::ShadowMapDataType)ShadowMapType) { case FShadowMapData2D::SHADOW_SIGNED_DISTANCE_FIELD_DATA: Data = new FShadowSignedDistanceFieldData2D(SizeX, SizeY); break; case FShadowMapData2D::SHADOW_SIGNED_DISTANCE_FIELD_DATA_QUANTIZED: Data = new FQuantizedShadowSignedDistanceFieldData2D(SizeX, SizeY); break; default: case FShadowMapData2D::SHADOW_FACTOR_DATA: case FShadowMapData2D::SHADOW_FACTOR_DATA_QUANTIZED: ensure(false); return; } Data->Serialize(&Ar); ShadowMapData.Add(Cast(Path.ResolveObject()), Data); } } else { for (auto& it : ShadowMapData) { FSoftObjectPath Path(it.Key); Ar << Path; int32 ShadowMapType = (int32)it.Value->GetType(); Ar << ShadowMapType; int32 SizeX = it.Value->GetSizeX(); int32 SizeY = it.Value->GetSizeY(); Ar << SizeX; Ar << SizeY; it.Value->Serialize(&Ar); } } } #undef LOCTEXT_NAMESPACE