// Copyright Epic Games, Inc. All Rights Reserved. #include "Scene/SceneCapturePhotoSet.h" #include "EngineUtils.h" #include "Misc/ScopedSlowTask.h" using namespace UE::Geometry; #define LOCTEXT_NAMESPACE "SceneCapture" FSceneCapturePhotoSet::FSceneCapturePhotoSet() { // These defaults are chosen so an unfamiliar user doesn't need to call the configuration functions to get a result ForEachCaptureType([this](ERenderCaptureType CaptureType) { bool bIsDisabled = ( CaptureType == ERenderCaptureType::DeviceDepth || CaptureType == ERenderCaptureType::Roughness || CaptureType == ERenderCaptureType::Specular || CaptureType == ERenderCaptureType::Metallic); PhotoSetStatus[CaptureType] = bIsDisabled ? ECaptureTypeStatus::Disabled : ECaptureTypeStatus::Pending; RenderCaptureConfig[CaptureType] = GetDefaultRenderCaptureConfig(CaptureType); }); } void FSceneCapturePhotoSet::SetCaptureSceneActors(UWorld* World, const TArray& Actors) { SetCaptureSceneActorsAndComponents(World, Actors, {}); } void FSceneCapturePhotoSet::SetCaptureSceneComponents(UWorld* World, const TArray& Components) { SetCaptureSceneActorsAndComponents(World, {}, Components); } void FSceneCapturePhotoSet::SetCaptureSceneActorsAndComponents(UWorld* World, const TArray& Actors, const TArray& Components) { if (this->TargetWorld != World || this->VisibleActors != Actors || this->VisibleComponents != Components) { ForEachCaptureType([this](ERenderCaptureType CaptureType) { if (PhotoSetStatus[CaptureType] != ECaptureTypeStatus::Disabled) { PhotoSetStatus[CaptureType] = ECaptureTypeStatus::Pending; } }); // Empty the photo sets because they rendered different actors EmptyAllPhotoSets(); // Empty the spatial photo parameters because these are computed from the bounding box of the actors PhotoSetParams.Empty(); } this->TargetWorld = World; this->VisibleActors = Actors; this->VisibleComponents = Components; } UWorld* FSceneCapturePhotoSet::GetCaptureTargetWorld() { return TargetWorld; } void FSceneCapturePhotoSet::SetSpatialPhotoParams(const TArray& SpatialParams) { // TODO Discard/reset on a per array element level rather than discarding everything when any viewpoint changed if (PhotoSetParams != SpatialParams) { ForEachCaptureType([this](ERenderCaptureType CaptureType) { if (PhotoSetStatus[CaptureType] != ECaptureTypeStatus::Disabled) { PhotoSetStatus[CaptureType] = ECaptureTypeStatus::Pending; } }); EmptyAllPhotoSets(); PhotoSetParams = SpatialParams; } } const TArray& FSceneCapturePhotoSet::GetSpatialPhotoParams() const { return PhotoSetParams; } void FSceneCapturePhotoSet::SetCaptureConfig(ERenderCaptureType CaptureType, const FRenderCaptureConfig& NewConfig) { if (RenderCaptureConfig[CaptureType] != NewConfig) { if (PhotoSetStatus[CaptureType] != ECaptureTypeStatus::Disabled) { PhotoSetStatus[CaptureType] = ECaptureTypeStatus::Pending; } EmptyPhotoSet(CaptureType); RenderCaptureConfig[CaptureType] = NewConfig; } } FRenderCaptureConfig FSceneCapturePhotoSet::GetCaptureConfig(ERenderCaptureType CaptureType) const { return RenderCaptureConfig[CaptureType]; } void FSceneCapturePhotoSet::DisableAllCaptureTypes() { ForEachCaptureType([this](ERenderCaptureType CaptureType) { SetCaptureTypeEnabled(CaptureType, false); }); } void FSceneCapturePhotoSet::SetCaptureTypeEnabled(ERenderCaptureType CaptureType, bool bEnable) { if (bEnable) { if (PhotoSetStatus[CaptureType] == ECaptureTypeStatus::Disabled) { PhotoSetStatus[CaptureType] = ECaptureTypeStatus::Pending; } } else { PhotoSetStatus[CaptureType] = ECaptureTypeStatus::Disabled; EmptyPhotoSet(CaptureType); } } FSceneCapturePhotoSet::ECaptureTypeStatus FSceneCapturePhotoSet::GetCaptureTypeStatus(ERenderCaptureType CaptureType) const { return PhotoSetStatus[CaptureType]; } FSceneCapturePhotoSet::FStatus FSceneCapturePhotoSet::GetSceneCaptureStatus() const { return PhotoSetStatus; } void FSceneCapturePhotoSet::Compute() { TRACE_CPUPROFILER_EVENT_SCOPE(SceneCapturePhotoSet::Compute); bWasCancelled = false; int NumPending = 0; ForEachCaptureType([this, &NumPending](ERenderCaptureType CaptureType) { NumPending += static_cast(PhotoSetStatus[CaptureType] == ECaptureTypeStatus::Pending); }); if (NumPending == 0) { return; } FScopedSlowTask Progress(static_cast(NumPending), LOCTEXT("CapturingScene", "Capturing Scene...")); Progress.MakeDialog(bAllowCancel); check(this->TargetWorld != nullptr); // Unregister all components to remove unwanted proxies from the scene. This was previously the only way to "hide" nanite meshes, now optional. TSet VisibleActorsSet(VisibleActors); TArray ActorsToRegister; if (bEnforceVisibilityViaUnregister) { for (TActorIterator Actor(TargetWorld); Actor; ++Actor) { if (!VisibleActorsSet.Contains(*Actor)) { Actor->UnregisterAllComponents(); ActorsToRegister.Add(*Actor); } } } ON_SCOPE_EXIT { // Workaround for Nanite scene proxies visibility // Reregister all components we previously unregistered for (AActor* Actor : ActorsToRegister) { Actor->RegisterAllComponents(); } }; FWorldRenderCapture RenderCapture; RenderCapture.SetWorld(TargetWorld); RenderCapture.SetVisibleActorsAndComponents(VisibleActors, VisibleComponents); RenderCapture.SetEnableWriteDebugImage(bWriteDebugImages, 0, DebugImagesFolderName); auto CapturePhoto3f = [this, &RenderCapture](ERenderCaptureType CaptureType, const FSpatialPhotoParams& Params) { FSpatialPhoto3f NewPhoto; NewPhoto.Frame = Params.Frame; NewPhoto.NearPlaneDist = Params.NearPlaneDist; NewPhoto.HorzFOVDegrees = Params.HorzFOVDegrees; NewPhoto.Dimensions = Params.Dimensions; // TODO Do something with the success boolean returned by RenderCapture.CaptureFromPosition FImageAdapter Image(&NewPhoto.Image); FRenderCaptureConfig Config = GetCaptureConfig(CaptureType); RenderCapture.SetDimensions(Params.Dimensions); RenderCapture.CaptureFromPosition(CaptureType, NewPhoto.Frame, NewPhoto.HorzFOVDegrees, NewPhoto.NearPlaneDist, Image, Config); GetPhotoSet3f(CaptureType).Add(MoveTemp(NewPhoto)); }; auto CapturePhoto1f = [this, &RenderCapture](ERenderCaptureType CaptureType, const FSpatialPhotoParams& Params) { FSpatialPhoto1f NewPhoto; NewPhoto.Frame = Params.Frame; NewPhoto.NearPlaneDist = Params.NearPlaneDist; NewPhoto.HorzFOVDegrees = Params.HorzFOVDegrees; NewPhoto.Dimensions = Params.Dimensions; // TODO Do something with the success boolean returned by RenderCapture.CaptureFromPosition FImageAdapter Image(&NewPhoto.Image); FRenderCaptureConfig Config = GetCaptureConfig(CaptureType); RenderCapture.SetDimensions(Params.Dimensions); RenderCapture.CaptureFromPosition(CaptureType, NewPhoto.Frame, NewPhoto.HorzFOVDegrees, NewPhoto.NearPlaneDist, Image, Config); GetPhotoSet1f(CaptureType).Add(MoveTemp(NewPhoto)); }; // Iterate by channel computing all the photos rather than by photo/viewpoint computing all the channels. // Note: This ordering means that some captures may have computed even if the computation is cancelled. auto CapturePhotoSet = [this, &Progress]( TFunctionRef CapturePhoto, ERenderCaptureType CaptureType, const FText& TaskMessage) { if (PhotoSetStatus[CaptureType] == ECaptureTypeStatus::Pending) { Progress.EnterProgressFrame(1.f); FScopedSlowTask PhotoSetProgress(PhotoSetParams.Num(), TaskMessage); EmptyPhotoSet(CaptureType); for (const FSpatialPhotoParams& Params : PhotoSetParams) { if (Progress.ShouldCancel()) { UE_LOG(LogGeometry, Display, TEXT("FSceneCapturePhotoSet: The pending '%s' step was cancelled"), *TaskMessage.ToString()); bWasCancelled = true; return; } PhotoSetProgress.EnterProgressFrame(1.f); CapturePhoto(CaptureType, Params); Progress.TickProgress(); } PhotoSetStatus[CaptureType] = ECaptureTypeStatus::Computed; } }; { TRACE_CPUPROFILER_EVENT_SCOPE(FSceneCapturePhotoSet::Compute_DeviceDepth); CapturePhotoSet(CapturePhoto1f, ERenderCaptureType::DeviceDepth, LOCTEXT("CapturingScene_DeviceDepth", "Capturing Device Depth")); } { TRACE_CPUPROFILER_EVENT_SCOPE(FSceneCapturePhotoSet::Compute_BaseColor); CapturePhotoSet(CapturePhoto3f, ERenderCaptureType::BaseColor, LOCTEXT("CapturingScene_BaseColor", "Capturing Base Color")); } { TRACE_CPUPROFILER_EVENT_SCOPE(FSceneCapturePhotoSet::Compute_WorldNormal); CapturePhotoSet(CapturePhoto3f, ERenderCaptureType::WorldNormal, LOCTEXT("CapturingScene_WorldNormal", "Capturing World Normal")); } { TRACE_CPUPROFILER_EVENT_SCOPE(FSceneCapturePhotoSet::Compute_CombinedMRS); CapturePhotoSet(CapturePhoto3f, ERenderCaptureType::CombinedMRS, LOCTEXT("CapturingScene_CombinedMRS", "Capturing Packed MRS")); } { TRACE_CPUPROFILER_EVENT_SCOPE(FSceneCapturePhotoSet::Compute_Metallic); CapturePhotoSet(CapturePhoto1f, ERenderCaptureType::Metallic, LOCTEXT("CapturingScene_Metallic", "Capturing Metallic")); } { TRACE_CPUPROFILER_EVENT_SCOPE(FSceneCapturePhotoSet::Compute_Roughness); CapturePhotoSet(CapturePhoto1f, ERenderCaptureType::Roughness, LOCTEXT("CapturingScene_Roughness", "Capturing Roughness")); } { TRACE_CPUPROFILER_EVENT_SCOPE(FSceneCapturePhotoSet::Compute_Specular); CapturePhotoSet(CapturePhoto1f, ERenderCaptureType::Specular, LOCTEXT("CapturingScene_Specular", "Capturing Specular")); } { TRACE_CPUPROFILER_EVENT_SCOPE(FSceneCapturePhotoSet::Compute_Emissive); CapturePhotoSet(CapturePhoto3f, ERenderCaptureType::Emissive, LOCTEXT("CapturingScene_Emissive", "Capturing Emissive")); } { TRACE_CPUPROFILER_EVENT_SCOPE(FSceneCapturePhotoSet::Compute_Opacity); CapturePhotoSet(CapturePhoto1f, ERenderCaptureType::Opacity, LOCTEXT("CapturingScene_Opacity", "Capturing Opacity")); } { TRACE_CPUPROFILER_EVENT_SCOPE(FSceneCapturePhotoSet::Compute_SubsurfaceColor); CapturePhotoSet(CapturePhoto3f, ERenderCaptureType::SubsurfaceColor, LOCTEXT("CapturingScene_SubsurfaceColor", "Capturing Subsurface Color")); } } void FSceneCapturePhotoSet::OptimizePhotoSets() { // todo: // 1) crop photos to regions with actual pixels // 2) pack into fewer photos (eg pack spec/rough/metallic) // 3) RLE encoding or other compression } FSceneCapturePhotoSet::FSceneSample::FSceneSample() { HaveValues = FRenderCaptureTypeFlags::None(); BaseColor = FVector3f(0, 0, 0); Roughness = 0.0f; Specular = 0.0f; Metallic = 0.0f; Emissive = FVector3f(0, 0, 0); Opacity = 0.0f; SubsurfaceColor = FVector3f(0, 0, 0); WorldNormal = FVector3f(0, 0, 1); DeviceDepth = 0.0f; // Since we use reverse z perspective projection the far plane corresponds to DeviceDepth == 0 } FVector3f FSceneCapturePhotoSet::FSceneSample::GetValue3f(ERenderCaptureType CaptureType) const { switch (CaptureType) { case ERenderCaptureType::BaseColor: return BaseColor; case ERenderCaptureType::WorldNormal: return WorldNormal; case ERenderCaptureType::Roughness: return Roughness * FVector3f::One(); case ERenderCaptureType::Metallic: return Metallic * FVector3f::One(); case ERenderCaptureType::Specular: return Specular * FVector3f::One(); case ERenderCaptureType::Emissive: return Emissive; case ERenderCaptureType::Opacity: return Opacity * FVector3f::One(); case ERenderCaptureType::SubsurfaceColor: return SubsurfaceColor; case ERenderCaptureType::DeviceDepth: return DeviceDepth * FVector3f::One(); default: ensure(false); } return FVector3f::Zero(); } FVector4f FSceneCapturePhotoSet::FSceneSample::GetValue4f(ERenderCaptureType CaptureType) const { switch (CaptureType) { case ERenderCaptureType::BaseColor: return FVector4f(BaseColor.X, BaseColor.Y, BaseColor.Z, 1.0f); case ERenderCaptureType::WorldNormal: return FVector4f(WorldNormal.X, WorldNormal.Y, WorldNormal.Z, 1.0f); case ERenderCaptureType::Roughness: return FVector4f(Roughness, Roughness, Roughness, 1.0f); case ERenderCaptureType::Metallic: return FVector4f(Metallic, Metallic, Metallic, 1.0f); case ERenderCaptureType::Specular: return FVector4f(Specular, Specular, Specular, 1.0f); case ERenderCaptureType::CombinedMRS: return FVector4f(Metallic, Roughness, Specular, 1.0f); case ERenderCaptureType::Emissive: return FVector4f(Emissive.X, Emissive.Y, Emissive.Z, 1.0f); case ERenderCaptureType::Opacity: return FVector4f(Opacity, Opacity, Opacity, 1.0f); case ERenderCaptureType::SubsurfaceColor: return FVector4f(SubsurfaceColor.X, SubsurfaceColor.Y, SubsurfaceColor.Z, 1.0f); case ERenderCaptureType::DeviceDepth: return FVector4f(DeviceDepth, DeviceDepth, DeviceDepth, 1.0f); default: ensure(false); } return FVector4f::Zero(); } bool FSceneCapturePhotoSet::ComputeSampleLocation( const FVector3d& Position, const FVector3d& Normal, const float ValidSampleDepthThreshold, TFunctionRef VisibilityFunction, int& PhotoIndex, FVector2d& PhotoCoords) const { double DotTolerance = -0.1; // dot should be negative for normal pointing towards photo if (ValidSampleDepthThreshold > 0) { check(DeviceDepthPhotoSet.Num() == PhotoSetParams.Num()); } PhotoIndex = IndexConstants::InvalidID; PhotoCoords = FVector2d(0., 0.); double MinDot = 1.0; int32 NumPhotos = PhotoSetParams.Num(); for (int32 Index = 0; Index < NumPhotos; ++Index) { const FSpatialPhotoParams& Params = PhotoSetParams[Index]; check(Params.Dimensions.IsSquare()); const FViewMatrices ViewMatrices = GetRenderCaptureViewMatrices(Params.Frame, Params.HorzFOVDegrees, Params.NearPlaneDist, Params.Dimensions); FFrame3d RenderFrame(Params.Frame.Origin, Params.Frame.Y(), Params.Frame.Z(), Params.Frame.X()); FVector3d ViewDirection = RenderFrame.Z(); double ViewDot = ViewDirection.Dot(Normal); if (ViewDot > DotTolerance || ViewDot > MinDot) { // The sample is facing away from the photo, or we found a photo more aligned with this sample continue; } FFrame3d ViewPlane = RenderFrame; ViewPlane.Origin += Params.NearPlaneDist * ViewDirection; double ViewPlaneWidthWorld = Params.NearPlaneDist * FMathd::Tan(Params.HorzFOVDegrees * 0.5 * FMathd::DegToRad); double ViewPlaneHeightWorld = ViewPlaneWidthWorld; // Shoot a ray from the camera position toward the sample position and find the hit point on photo plane constexpr int NormalAxisIndex = 2; FVector3d RayOrigin = RenderFrame.Origin; FVector3d RayDir = Normalized(Position - RayOrigin); FVector3d HitPoint; bool bHit = ViewPlane.RayPlaneIntersection(RayOrigin, RayDir, NormalAxisIndex, HitPoint); if (bHit) { FVector2d DeviceXY; DeviceXY.X = (HitPoint - ViewPlane.Origin).Dot(ViewPlane.X()) / ViewPlaneWidthWorld; DeviceXY.Y = (HitPoint - ViewPlane.Origin).Dot(ViewPlane.Y()) / ViewPlaneHeightWorld; if (FMathd::Abs(DeviceXY.X) < 1 && FMathd::Abs(DeviceXY.Y) < 1) { // Shoot a ray from the sample position toward the camera position checking occlusion bool bVisible = VisibilityFunction(Position, RayOrigin); if (bVisible) { FVector2d UVCoords = FRenderCaptureCoordinateConverter2D::DeviceToUV(DeviceXY); if (ValidSampleDepthThreshold > 0) { // Look up the device depth from the depth photo set, these values are from 0 (far plane) to // 1 (near plane). We skip points which would unproject to the far plane, which is positioned at // infinity, we also do not interpolate the depth values, doing so is not a good approximation // of the underlying scene float DeviceZ = DeviceDepthPhotoSet.Get(Index).Image.NearestSampleUV(UVCoords); if (DeviceZ > 0) { // Compute the pixel position in world space to use it to compute a depth according to the render FVector3d PixelPositionDevice{DeviceXY, DeviceZ}; FVector4d PixelPositionWorld = ViewMatrices.GetInvViewProjectionMatrix().TransformPosition(PixelPositionDevice); PixelPositionWorld /= PixelPositionWorld.W; // Compare the depth of the sample with the depth of the pixel and consider the sample invalid // if these do not match closely enough. This fixes artefacts which occur when sample ray just // misses an obstruction and hits a pixel where the color was set by a slightly different ray, // through the pixel center, which does hit the obstruction. This problem occurs because the // depth capture was obtained by renderering the the source meshes but the visiblity function // works on the target mesh float PixelDepth = (RayOrigin - FVector3d(PixelPositionWorld)).Length(); float SampleDepth = (RayOrigin - Position).Length(); if (FMath::Abs(PixelDepth - SampleDepth) < ValidSampleDepthThreshold) { PhotoCoords.X = UVCoords.X * (double)Params.Dimensions.GetWidth(); PhotoCoords.Y = UVCoords.Y * (double)Params.Dimensions.GetHeight(); PhotoIndex = Index; MinDot = ViewDot; } } // Test DeviceZ > 0 } else { PhotoCoords.X = UVCoords.X * (double)Params.Dimensions.GetWidth(); PhotoCoords.Y = UVCoords.Y * (double)Params.Dimensions.GetHeight(); PhotoIndex = Index; MinDot = ViewDot; } } // Test bVisible } // Test UVCoords in (-1,1)x(-1,1) } // Hit photo plane } // Photo loop return PhotoIndex != IndexConstants::InvalidID; } bool FSceneCapturePhotoSet::ComputeSample( const FRenderCaptureTypeFlags& SampleChannels, const FVector3d& Position, const FVector3d& Normal, TFunctionRef VisibilityFunction, FSceneSample& DefaultsInResultsOut) const { // This could be much more efficient if (eg) we knew that all the photo sets have // the same captures, then the query only has to be done once and can be used to sample each specific photo // This is implemented in the other ComputeSample overload if (SampleChannels.bBaseColor) { DefaultsInResultsOut.BaseColor = BaseColorPhotoSet.ComputeSample(Position, Normal, VisibilityFunction, DefaultsInResultsOut.BaseColor); DefaultsInResultsOut.HaveValues.bBaseColor = true; } if (SampleChannels.bRoughness) { DefaultsInResultsOut.Roughness = RoughnessPhotoSet.ComputeSample(Position, Normal, VisibilityFunction, DefaultsInResultsOut.Roughness); DefaultsInResultsOut.HaveValues.bRoughness = true; } if (SampleChannels.bSpecular) { DefaultsInResultsOut.Specular = SpecularPhotoSet.ComputeSample(Position, Normal, VisibilityFunction, DefaultsInResultsOut.Specular); DefaultsInResultsOut.HaveValues.bSpecular = true; } if (SampleChannels.bMetallic) { DefaultsInResultsOut.Metallic = MetallicPhotoSet.ComputeSample(Position, Normal, VisibilityFunction, DefaultsInResultsOut.Metallic); DefaultsInResultsOut.HaveValues.bMetallic = true; } if (SampleChannels.bCombinedMRS) { FVector3f MRSValue(DefaultsInResultsOut.Metallic, DefaultsInResultsOut.Roughness, DefaultsInResultsOut.Specular); MRSValue = PackedMRSPhotoSet.ComputeSample(Position, Normal, VisibilityFunction, MRSValue); DefaultsInResultsOut.Metallic = MRSValue.X; DefaultsInResultsOut.Roughness = MRSValue.Y; DefaultsInResultsOut.Specular = MRSValue.Z; DefaultsInResultsOut.HaveValues.bMetallic = true; DefaultsInResultsOut.HaveValues.bRoughness = true; DefaultsInResultsOut.HaveValues.bSpecular = true; } if (SampleChannels.bEmissive) { DefaultsInResultsOut.Emissive = EmissivePhotoSet.ComputeSample(Position, Normal, VisibilityFunction, DefaultsInResultsOut.Emissive); DefaultsInResultsOut.HaveValues.bEmissive = true; } if (SampleChannels.bOpacity) { DefaultsInResultsOut.Opacity = OpacityPhotoSet.ComputeSample(Position, Normal, VisibilityFunction, DefaultsInResultsOut.Opacity); DefaultsInResultsOut.HaveValues.bOpacity = true; } if (SampleChannels.bSubsurfaceColor) { DefaultsInResultsOut.SubsurfaceColor = SubsurfaceColorPhotoSet.ComputeSample(Position, Normal, VisibilityFunction, DefaultsInResultsOut.SubsurfaceColor); DefaultsInResultsOut.HaveValues.bSubsurfaceColor = true; } if (SampleChannels.bWorldNormal) { DefaultsInResultsOut.WorldNormal = WorldNormalPhotoSet.ComputeSample(Position, Normal, VisibilityFunction, DefaultsInResultsOut.WorldNormal); DefaultsInResultsOut.HaveValues.bWorldNormal = true; } return true; } void FSceneCapturePhotoSet::GetSceneSamples(FSceneSamples& OutSamples) { if (!ensure(PhotoSetStatus.DeviceDepth == ECaptureTypeStatus::Computed)) { return; } TRACE_CPUPROFILER_EVENT_SCOPE(SceneCapturePhotoSet::GetSceneSamples); for (int32 PhotoIndex = 0; PhotoIndex < GetSpatialPhotoParams().Num(); ++PhotoIndex) { const FSpatialPhotoParams& Params = GetSpatialPhotoParams()[PhotoIndex]; const FViewMatrices ViewMatrices = GetRenderCaptureViewMatrices(Params.Frame, Params.HorzFOVDegrees, Params.NearPlaneDist, Params.Dimensions); for (int64 PixelLinearIndex = 0; PixelLinearIndex < Params.Dimensions.Num(); ++PixelLinearIndex) { const FSpatialPhoto1f& DeviceDepthPhoto = GetDeviceDepthPhotoSet().Get(PhotoIndex); const float DeviceZ = DeviceDepthPhoto.Image.GetPixel(PixelLinearIndex); const bool bValidDeviceZ = (DeviceZ > 0) && (DeviceZ < 1); if (bValidDeviceZ) // Skip samples on the near/far plane { bool bComputePointWorld = OutSamples.WorldPoint != nullptr; bool bComputeNormalWorld = false; if (PhotoSetStatus.WorldNormal == ECaptureTypeStatus::Computed) { bComputePointWorld = bComputePointWorld || (OutSamples.WorldOrientedPoints != nullptr); bComputeNormalWorld = (OutSamples.WorldNormal != nullptr) || (OutSamples.WorldOrientedPoints != nullptr); } FVector3f PointWorld; if (bComputePointWorld) { // Map from pixel space to normalized device coordinates FVector2d DeviceXY = FRenderCaptureCoordinateConverter2D::PixelToDevice( Params.Dimensions.GetCoords(PixelLinearIndex), Params.Dimensions.GetWidth(), Params.Dimensions.GetHeight()); // Map from normalized device coordinates to world coordinates FVector3d PointDevice(DeviceXY, DeviceZ); const FVector4d PointWorld4 = ViewMatrices.GetInvViewProjectionMatrix().TransformPosition(PointDevice); // Convert from Homogenous to Cartesian coordinates PointWorld.X = static_cast(PointWorld4.X / PointWorld4.W); PointWorld.Y = static_cast(PointWorld4.Y / PointWorld4.W); PointWorld.Z = static_cast(PointWorld4.Z / PointWorld4.W); } FVector3f NormalWorld; if (bComputeNormalWorld) { // Map normal from color components [0,1] to world components [-1,1] const FSpatialPhoto3f& NormalPhoto = GetWorldNormalPhotoSet().Get(PhotoIndex); const FVector4f NormalColor = NormalPhoto.Image.GetPixel(PixelLinearIndex); NormalWorld = 2.f * (FVector3f(NormalColor) - FVector3f(.5f)); } if (OutSamples.WorldPoint) { OutSamples.WorldPoint->Add(PointWorld); } if (PhotoSetStatus.WorldNormal == ECaptureTypeStatus::Computed) { if (OutSamples.WorldNormal) { OutSamples.WorldNormal->Add(NormalWorld); } if (OutSamples.WorldOrientedPoints) { OutSamples.WorldOrientedPoints->Add(FFrame3f(PointWorld, NormalWorld)); } } if (PhotoSetStatus.Metallic == ECaptureTypeStatus::Computed && OutSamples.Metallic) { OutSamples.Metallic->Add(GetMetallicPhotoSet().Get(PhotoIndex).Image.GetPixel(PixelLinearIndex)); } if (PhotoSetStatus.Roughness == ECaptureTypeStatus::Computed && OutSamples.Roughness) { OutSamples.Roughness->Add(GetRoughnessPhotoSet().Get(PhotoIndex).Image.GetPixel(PixelLinearIndex)); } if (PhotoSetStatus.Specular == ECaptureTypeStatus::Computed && OutSamples.Specular) { OutSamples.Specular->Add(GetSpecularPhotoSet().Get(PhotoIndex).Image.GetPixel(PixelLinearIndex)); } if (PhotoSetStatus.CombinedMRS == ECaptureTypeStatus::Computed && OutSamples.PackedMRS) { OutSamples.PackedMRS->Add(GetPackedMRSPhotoSet().Get(PhotoIndex).Image.GetPixel(PixelLinearIndex)); } if (PhotoSetStatus.Emissive == ECaptureTypeStatus::Computed && OutSamples.Emissive) { OutSamples.Emissive->Add(GetEmissivePhotoSet().Get(PhotoIndex).Image.GetPixel(PixelLinearIndex)); } if (PhotoSetStatus.BaseColor == ECaptureTypeStatus::Computed && OutSamples.BaseColor) { OutSamples.BaseColor->Add(GetBaseColorPhotoSet().Get(PhotoIndex).Image.GetPixel(PixelLinearIndex)); } if (PhotoSetStatus.SubsurfaceColor == ECaptureTypeStatus::Computed && OutSamples.SubsurfaceColor) { OutSamples.SubsurfaceColor->Add(GetSubsurfaceColorPhotoSet().Get(PhotoIndex).Image.GetPixel(PixelLinearIndex)); } if (PhotoSetStatus.Opacity == ECaptureTypeStatus::Computed && OutSamples.Opacity) { OutSamples.Opacity->Add(GetOpacityPhotoSet().Get(PhotoIndex).Image.GetPixel(PixelLinearIndex)); } } // bValidDeviceZ } // PixelLinearIndex } // PhotoIndex } void FSceneCapturePhotoSet::SetEnableVisibilityByUnregisterMode(bool bEnable) { bEnforceVisibilityViaUnregister = bEnable; } void FSceneCapturePhotoSet::EmptyAllPhotoSets() { ForEachCaptureType([this](ERenderCaptureType CaptureType) { EmptyPhotoSet(CaptureType); }); } void FSceneCapturePhotoSet::SetEnableWriteDebugImages(bool bEnable, FString FolderName) { bWriteDebugImages = bEnable; if (FolderName.Len() > 0) { DebugImagesFolderName = FolderName; } } FSpatialPhotoSet1f& FSceneCapturePhotoSet::GetPhotoSet1f(ERenderCaptureType CaptureType) { switch (CaptureType) { case ERenderCaptureType::Roughness: return RoughnessPhotoSet; case ERenderCaptureType::Metallic: return MetallicPhotoSet; case ERenderCaptureType::Specular: return SpecularPhotoSet; case ERenderCaptureType::Opacity: return OpacityPhotoSet; case ERenderCaptureType::DeviceDepth: return DeviceDepthPhotoSet; default: ensure(false); } return RoughnessPhotoSet; } FSpatialPhotoSet3f& FSceneCapturePhotoSet::GetPhotoSet3f(ERenderCaptureType CaptureType) { switch (CaptureType) { case ERenderCaptureType::BaseColor: return BaseColorPhotoSet; case ERenderCaptureType::CombinedMRS: return PackedMRSPhotoSet; case ERenderCaptureType::WorldNormal: return WorldNormalPhotoSet; case ERenderCaptureType::Emissive: return EmissivePhotoSet; case ERenderCaptureType::SubsurfaceColor: return SubsurfaceColorPhotoSet; default: ensure(false); } return BaseColorPhotoSet; } void FSceneCapturePhotoSet::EmptyPhotoSet(ERenderCaptureType CaptureType) { // Set the functions to empty the photo sets switch (CaptureType) { case ERenderCaptureType::BaseColor: BaseColorPhotoSet.Empty(); break; case ERenderCaptureType::WorldNormal: WorldNormalPhotoSet.Empty(); break; case ERenderCaptureType::Roughness: RoughnessPhotoSet.Empty(); break; case ERenderCaptureType::Metallic: MetallicPhotoSet.Empty(); break; case ERenderCaptureType::Specular: SpecularPhotoSet.Empty(); break; case ERenderCaptureType::Emissive: EmissivePhotoSet.Empty(); break; case ERenderCaptureType::CombinedMRS: PackedMRSPhotoSet.Empty(); break; case ERenderCaptureType::Opacity: OpacityPhotoSet.Empty(); break; case ERenderCaptureType::SubsurfaceColor: SubsurfaceColorPhotoSet.Empty(); break; case ERenderCaptureType::DeviceDepth: DeviceDepthPhotoSet.Empty(); break; default: ensure(false); } } // TODO Deprecate this function and replace it with ComputeRenderCaptureCamerasForBox TArray UE::Geometry::ComputeStandardExteriorSpatialPhotoParameters( UWorld* Unused, const TArray& Actors, const TArray& Components, FImageDimensions PhotoDimensions, double HorizontalFOVDegrees, double NearPlaneDist, bool bFaces, bool bUpperCorners, bool bLowerCorners, bool bUpperEdges, bool bSideEdges) { if (Actors.IsEmpty() && Components.IsEmpty()) { return {}; } TArray Directions; if (bFaces) { Directions.Add(FVector3d::UnitX()); Directions.Add(-FVector3d::UnitX()); Directions.Add(FVector3d::UnitY()); Directions.Add(-FVector3d::UnitY()); Directions.Add(FVector3d::UnitZ()); Directions.Add(-FVector3d::UnitZ()); } if (bUpperCorners) { Directions.Add(Normalized(FVector3d(1, 1, -1))); Directions.Add(Normalized(FVector3d(-1, 1, -1))); Directions.Add(Normalized(FVector3d(1, -1, -1))); Directions.Add(Normalized(FVector3d(-1, -1, -1))); } if (bLowerCorners) { Directions.Add(Normalized(FVector3d(1, 1, 1))); Directions.Add(Normalized(FVector3d(-1, 1, 1))); Directions.Add(Normalized(FVector3d(1, -1, 1))); Directions.Add(Normalized(FVector3d(-1, -1, 1))); } if (bUpperEdges) { Directions.Add(Normalized(FVector3d(-1, 0, -1))); Directions.Add(Normalized(FVector3d(1, 0, -1))); Directions.Add(Normalized(FVector3d(0, -1, -1))); Directions.Add(Normalized(FVector3d(0, 1, -1))); } // TODO We are missing bLowerEdges! if (bSideEdges) { Directions.Add(Normalized(FVector3d(1, 1, 0))); Directions.Add(Normalized(FVector3d(-1, 1, 0))); Directions.Add(Normalized(FVector3d(1, -1, 0))); Directions.Add(Normalized(FVector3d(-1, -1, 0))); } // Compute a sphere bounding the give actors so we can use it to position the render capture viewpoints // Note: We use FWorldRenderCapture to do this but we are not going to render anything in this function FSphere RenderSphere; { FWorldRenderCapture RenderCapture; RenderCapture.SetVisibleActorsAndComponents(Actors, Components); RenderSphere = RenderCapture.ComputeContainingRenderSphere(HorizontalFOVDegrees); } TArray Result; int32 NumDirections = Directions.Num(); for (int32 di = 0; di < NumDirections; ++di) { FVector3d ViewDirection = Directions[di]; ViewDirection.Normalize(); FSpatialPhotoParams Params; Params.NearPlaneDist = NearPlaneDist; Params.HorzFOVDegrees = HorizontalFOVDegrees; Params.Dimensions = PhotoDimensions; // TODO Align the frame with the renderer coordinate system then remove the axis swapping in WorldRenderCapture.cpp Params.Frame.AlignAxis(0, ViewDirection); Params.Frame.ConstrainedAlignAxis(2, FVector3d::UnitZ(), Params.Frame.X()); Params.Frame.Origin = RenderSphere.Center; Params.Frame.Origin -= RenderSphere.W * Params.Frame.X(); Result.Add(Params); } return Result; } #undef LOCTEXT_NAMESPACE