You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#jira UE-195055 #rb Jimmy.Andrews, lonnie.li [CL 28705036 by matija kecman in ue5-main branch]
879 lines
31 KiB
C++
879 lines
31 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Baking/RenderCaptureFunctions.h"
|
|
#include "Scene/SceneCapturePhotoSet.h"
|
|
#include "Sampling/MeshMapBaker.h"
|
|
#include "Sampling/RenderCaptureMapEvaluator.h"
|
|
#include "AssetUtils/Texture2DBuilder.h"
|
|
|
|
#include "Engine/Texture2D.h"
|
|
#include "GameFramework/Actor.h"
|
|
#include "Algo/NoneOf.h"
|
|
#include "Algo/AnyOf.h"
|
|
#include "Misc/ScopedSlowTask.h"
|
|
|
|
|
|
using namespace UE::Geometry;
|
|
|
|
#define LOCTEXT_NAMESPACE "RenderCaptureFunctions"
|
|
|
|
|
|
|
|
FRenderCaptureOcclusionHandler::FRenderCaptureOcclusionHandler(FImageDimensions Dimensions)
|
|
{
|
|
SampleStats.SetDimensions(Dimensions);
|
|
SampleStats.Clear({});
|
|
}
|
|
|
|
void FRenderCaptureOcclusionHandler::RegisterSample(const FVector2i& ImageCoords, bool bSampleValid)
|
|
{
|
|
checkSlow(SampleStats.GetDimensions().IsValidCoords(ImageCoords));
|
|
if (bSampleValid)
|
|
{
|
|
SampleStats.GetPixel(ImageCoords).NumValid += 1;
|
|
}
|
|
else
|
|
{
|
|
SampleStats.GetPixel(ImageCoords).NumInvalid += 1;
|
|
}
|
|
}
|
|
|
|
void FRenderCaptureOcclusionHandler::PushInfillRequired(bool bInfillRequired)
|
|
{
|
|
InfillRequired.Add(bInfillRequired);
|
|
}
|
|
|
|
void FRenderCaptureOcclusionHandler::ComputeAndApplyInfill(TArray<TUniquePtr<TImageBuilder<FVector4f>>>& Images)
|
|
{
|
|
check(Images.Num() == InfillRequired.Num());
|
|
|
|
if (Images.IsEmpty() || Algo::NoneOf(InfillRequired))
|
|
{
|
|
return;
|
|
}
|
|
|
|
ComputeInfill();
|
|
|
|
for (int ImageIndex = 0; ImageIndex < Images.Num(); ImageIndex++)
|
|
{
|
|
if (InfillRequired[ImageIndex])
|
|
{
|
|
ApplyInfill(*Images[ImageIndex]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FRenderCaptureOcclusionHandler::ComputeInfill()
|
|
{
|
|
// Find pixels that need infill
|
|
TArray<FVector2i> MissingPixels;
|
|
FCriticalSection MissingPixelsLock;
|
|
ParallelFor(SampleStats.GetDimensions().GetHeight(), [this, &MissingPixels, &MissingPixelsLock](int32 Y)
|
|
{
|
|
for (int32 X = 0; X < SampleStats.GetDimensions().GetWidth(); X++)
|
|
{
|
|
const FSampleStats& Stats = SampleStats.GetPixel(X, Y);
|
|
// TODO experiment with other classifications
|
|
if (Stats.NumInvalid > 0 && Stats.NumValid == 0)
|
|
{
|
|
MissingPixelsLock.Lock();
|
|
MissingPixels.Add(FVector2i(X, Y));
|
|
MissingPixelsLock.Unlock();
|
|
}
|
|
}
|
|
});
|
|
|
|
auto DummyNormalizeStatsFunc = [](FSampleStats SumValue, int32 Count)
|
|
{
|
|
// The return value must be different from MissingValue below so that ComputeInfill works correctly
|
|
return FSampleStats{TNumericLimits<uint16>::Max(), TNumericLimits<uint16>::Max()};
|
|
};
|
|
|
|
// This must be the same as the value of exterior pixels, otherwise infill will spread the exterior values into the texture
|
|
FSampleStats MissingValue{0, 0};
|
|
Infill.ComputeInfill(SampleStats, MissingPixels, MissingValue, DummyNormalizeStatsFunc);
|
|
}
|
|
|
|
void FRenderCaptureOcclusionHandler::ApplyInfill(TImageBuilder<FVector4f>& Image) const
|
|
{
|
|
auto NormalizeFunc = [](FVector4f SumValue, int32 Count)
|
|
{
|
|
float InvSum = (Count == 0) ? 1.0f : (1.0f / Count);
|
|
return FVector4f(SumValue.X * InvSum, SumValue.Y * InvSum, SumValue.Z * InvSum, 1.0f);
|
|
};
|
|
|
|
Infill.ApplyInfill<FVector4f>(Image, NormalizeFunc);
|
|
}
|
|
|
|
bool FRenderCaptureOcclusionHandler::FSampleStats::operator==(const FSampleStats& Other) const
|
|
{
|
|
return (NumValid == Other.NumValid) && (NumInvalid == Other.NumInvalid);
|
|
}
|
|
|
|
bool FRenderCaptureOcclusionHandler::FSampleStats::operator!=(const FSampleStats& Other) const
|
|
{
|
|
return !(*this == Other);
|
|
}
|
|
|
|
FRenderCaptureOcclusionHandler::FSampleStats& FRenderCaptureOcclusionHandler::FSampleStats::operator+=(const FSampleStats& Other)
|
|
{
|
|
NumValid += Other.NumValid;
|
|
NumInvalid += Other.NumInvalid;
|
|
return *this;
|
|
}
|
|
|
|
FRenderCaptureOcclusionHandler::FSampleStats FRenderCaptureOcclusionHandler::FSampleStats::Zero()
|
|
{
|
|
return FSampleStats{0, 0};
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
FSceneCapturePhotoSetSampler::FSceneCapturePhotoSetSampler(
|
|
FSceneCapturePhotoSet* SceneCapture,
|
|
float ValidSampleDepthThreshold,
|
|
const FDynamicMesh3* Mesh,
|
|
const FDynamicMeshAABBTree3* Spatial,
|
|
const FMeshTangentsd* Tangents) :
|
|
FMeshBakerDynamicMeshSampler(Mesh, Spatial, Tangents),
|
|
SceneCapture(SceneCapture),
|
|
ValidSampleDepthThreshold(ValidSampleDepthThreshold)
|
|
{
|
|
check(SceneCapture != nullptr);
|
|
check(Mesh != nullptr);
|
|
check(Spatial != nullptr);
|
|
check(Tangents != nullptr);
|
|
|
|
bool bHasDepth = SceneCapture->GetCaptureTypeStatus(ERenderCaptureType::DeviceDepth) == FSceneCapturePhotoSet::ECaptureTypeStatus::Computed;
|
|
if (bHasDepth)
|
|
{
|
|
ensure(ValidSampleDepthThreshold > 0); // We only need the depth capture if this threshold is positive
|
|
}
|
|
|
|
// Compute an offset shift surface positions toward the render camera position so we don't immediately self intersect
|
|
// The Max expression ensures the code works when the base mesh is 2D along one coordinate direction
|
|
const double RayOffsetHackDist((double)(100.0 * FMathf::ZeroTolerance * FMath::Max(Mesh->GetBounds().MinDim(), 0.01)));
|
|
|
|
VisibilityFunction = [this, RayOffsetHackDist](const FVector3d& SurfPos, const FVector3d& ViewPos)
|
|
{
|
|
FVector3d RayDir = ViewPos - SurfPos;
|
|
double Dist = Normalize(RayDir);
|
|
FVector3d RayOrigin = SurfPos + RayOffsetHackDist * RayDir;
|
|
int32 HitTID = DetailSpatial->FindNearestHitTriangle(FRay3d(RayOrigin, RayDir), IMeshSpatial::FQueryOptions(Dist));
|
|
return (HitTID == IndexConstants::InvalidID);
|
|
};
|
|
}
|
|
|
|
bool FSceneCapturePhotoSetSampler::SupportsCustomCorrespondence() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void* FSceneCapturePhotoSetSampler::ComputeCustomCorrespondence(const FMeshUVSampleInfo& SampleInfo, FMeshMapEvaluator::FCorrespondenceSample& Sample) const
|
|
{
|
|
|
|
// Perform a ray-cast to determine which photo/coordinate, if any, should be sampled
|
|
int PhotoIndex;
|
|
FVector2d PhotoCoords;
|
|
SceneCapture->ComputeSampleLocation(
|
|
Sample.BaseSample.SurfacePoint,
|
|
Sample.BaseNormal,
|
|
ValidSampleDepthThreshold,
|
|
VisibilityFunction,
|
|
PhotoIndex,
|
|
PhotoCoords);
|
|
|
|
// Store the photo coordinates and index in the correspondence sample
|
|
Sample.DetailMesh = SceneCapture;
|
|
Sample.DetailTriID = PhotoIndex;
|
|
Sample.DetailBaryCoords.X = PhotoCoords.X;
|
|
Sample.DetailBaryCoords.Y = PhotoCoords.Y;
|
|
|
|
// This will be set to Sample.DetailMesh but we can already do that internally so it's kindof redundant
|
|
return SceneCapture;
|
|
}
|
|
|
|
bool FSceneCapturePhotoSetSampler::IsValidCorrespondence(const FMeshMapEvaluator::FCorrespondenceSample& Sample) const
|
|
{
|
|
return Sample.DetailTriID != IndexConstants::InvalidID;
|
|
}
|
|
|
|
|
|
|
|
bool FSceneCaptureConfig::operator==(const FSceneCaptureConfig& Other) const
|
|
{
|
|
return Flags == Other.Flags
|
|
&& RenderCaptureImageSize == Other.RenderCaptureImageSize
|
|
&& bAntiAliasing == Other.bAntiAliasing
|
|
&& FieldOfViewDegrees == Other.FieldOfViewDegrees
|
|
&& NearPlaneDist == Other.NearPlaneDist;
|
|
}
|
|
|
|
bool FSceneCaptureConfig::operator!=(const FSceneCaptureConfig& Other) const
|
|
{
|
|
return !this->operator==(Other);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void UE::Geometry::ConfigureSceneCapture(
|
|
FSceneCapturePhotoSet& SceneCapture,
|
|
const TArray<TObjectPtr<AActor>>& Actors,
|
|
const FSceneCaptureConfig& Options,
|
|
bool bAllowCancel)
|
|
{
|
|
ForEachCaptureType([&SceneCapture, &Options](ERenderCaptureType CaptureType)
|
|
{
|
|
const bool bCaptureTypeEnabled = Options.Flags[CaptureType];
|
|
SceneCapture.SetCaptureTypeEnabled(CaptureType, bCaptureTypeEnabled);
|
|
|
|
FRenderCaptureConfig Config;
|
|
Config.bAntiAliasing = (CaptureType == ERenderCaptureType::DeviceDepth ? false : Options.bAntiAliasing);
|
|
SceneCapture.SetCaptureConfig(CaptureType, Config);
|
|
});
|
|
|
|
UWorld* World = Actors.IsEmpty() ? nullptr : Actors[0]->GetWorld();
|
|
|
|
SceneCapture.SetCaptureSceneActors(World, Actors);
|
|
|
|
const TArray<FSpatialPhotoParams> SpatialParams = ComputeStandardExteriorSpatialPhotoParameters(
|
|
World,
|
|
Actors,
|
|
TArray<UActorComponent*>(),
|
|
FImageDimensions(Options.RenderCaptureImageSize, Options.RenderCaptureImageSize),
|
|
Options.FieldOfViewDegrees,
|
|
Options.NearPlaneDist,
|
|
true, true, true, true, true);
|
|
|
|
SceneCapture.SetSpatialPhotoParams(SpatialParams);
|
|
|
|
SceneCapture.SetAllowCancel(bAllowCancel);
|
|
}
|
|
|
|
|
|
|
|
UE::Geometry::FRenderCaptureTypeFlags UE::Geometry::UpdateSceneCapture(
|
|
FSceneCapturePhotoSet& SceneCapture,
|
|
const TArray<TObjectPtr<AActor>>& Actors,
|
|
const FSceneCaptureConfig& DesiredConfig,
|
|
bool bAllowCancel)
|
|
{
|
|
const FSceneCapturePhotoSet::FStatus PreConfigStatus = SceneCapture.GetSceneCaptureStatus();
|
|
ConfigureSceneCapture(SceneCapture, Actors, DesiredConfig, true);
|
|
const FSceneCapturePhotoSet::FStatus PreComputeStatus = SceneCapture.GetSceneCaptureStatus();
|
|
SceneCapture.Compute();
|
|
const FSceneCapturePhotoSet::FStatus PostComputeStatus = SceneCapture.GetSceneCaptureStatus();
|
|
|
|
const FSceneCaptureConfig AchievedConfig = GetSceneCaptureConfig(SceneCapture);
|
|
|
|
// DesiredConfig and AchievedConfig mismatch iff the SceneCapture was cancelled. Also, the mismatch should only be in the Flags member
|
|
ensure(SceneCapture.Cancelled() == (AchievedConfig != DesiredConfig));
|
|
ensure(AchievedConfig.RenderCaptureImageSize == DesiredConfig.RenderCaptureImageSize);
|
|
ensure(AchievedConfig.FieldOfViewDegrees == DesiredConfig.FieldOfViewDegrees);
|
|
ensure(AchievedConfig.bAntiAliasing == DesiredConfig.bAntiAliasing);
|
|
ensure(AchievedConfig.NearPlaneDist == DesiredConfig.NearPlaneDist);
|
|
|
|
// Deduce which captures were changed in this scene capture update
|
|
FRenderCaptureTypeFlags UpdatedCaptures;
|
|
ForEachCaptureType([&UpdatedCaptures, &PreConfigStatus, &PreComputeStatus, &PostComputeStatus] (ERenderCaptureType CaptureType)
|
|
{
|
|
using EStatus = FSceneCapturePhotoSet::ECaptureTypeStatus;
|
|
|
|
// This boolean is true in two scenarios:
|
|
// 1. Computed -> Disabled occurs when the CaptureType was disabled by the DesiredConfig
|
|
// 2. Computed -> Pending occurs when the SceneCapture was cancelled while/before the CaptureType was computed
|
|
bool bCleared = PreConfigStatus[CaptureType] == EStatus::Computed && PostComputeStatus[CaptureType] != EStatus::Computed;
|
|
|
|
// This boolean is true in two scenarios, in both cases the PreComputeStatus of the given CaptureType is Pending
|
|
// 1. DesiredConfig enabled a new CaptureType
|
|
// 2. DesiredConfig requires an already computed CaptureType to be recomputed
|
|
bool bComputed = PreComputeStatus[CaptureType] == EStatus::Pending && PostComputeStatus[CaptureType] == EStatus::Computed;
|
|
|
|
UpdatedCaptures[CaptureType] = bCleared || bComputed;
|
|
});
|
|
|
|
return UpdatedCaptures;
|
|
}
|
|
|
|
|
|
|
|
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
TUniquePtr<FSceneCapturePhotoSet> UE::Geometry::CapturePhotoSet(
|
|
const TArray<TObjectPtr<AActor>>& Actors,
|
|
const FRenderCaptureOptions& Options,
|
|
FRenderCaptureUpdate& Update,
|
|
bool bAllowCancel)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(CapturePhotoSet);
|
|
|
|
TUniquePtr<FSceneCapturePhotoSet> SceneCapture = MakeUnique<FSceneCapturePhotoSet>();
|
|
|
|
Update = UpdatePhotoSets(SceneCapture, Actors, Options, bAllowCancel);
|
|
|
|
return SceneCapture;
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
FRenderCaptureUpdate UE::Geometry::UpdatePhotoSets(
|
|
const TUniquePtr<FSceneCapturePhotoSet>& SceneCapture,
|
|
const TArray<TObjectPtr<AActor>>& Actors,
|
|
const FRenderCaptureOptions& Options,
|
|
bool bAllowCancel)
|
|
{
|
|
FSceneCaptureConfig Config;
|
|
Config.RenderCaptureImageSize = Options.RenderCaptureImageSize;
|
|
Config.bAntiAliasing = Options.bAntiAliasing;
|
|
Config.FieldOfViewDegrees = Options.FieldOfViewDegrees;
|
|
Config.NearPlaneDist = Options.NearPlaneDist;
|
|
Config.Flags.bBaseColor = Options.bBakeBaseColor;
|
|
Config.Flags.bRoughness = Options.bBakeRoughness;
|
|
Config.Flags.bMetallic = Options.bBakeMetallic;
|
|
Config.Flags.bSpecular = Options.bBakeSpecular;
|
|
Config.Flags.bEmissive = Options.bBakeEmissive;
|
|
Config.Flags.bWorldNormal = Options.bBakeNormalMap;
|
|
Config.Flags.bOpacity = Options.bBakeOpacity;
|
|
Config.Flags.bSubsurfaceColor = Options.bBakeSubsurfaceColor;
|
|
Config.Flags.bDeviceDepth = Options.bBakeDeviceDepth;
|
|
Config.Flags.bCombinedMRS = Options.bUsePackedMRS;
|
|
|
|
FRenderCaptureTypeFlags UpdatedCaptures = UpdateSceneCapture(SceneCapture, Actors, Config, bAllowCancel);
|
|
|
|
FRenderCaptureUpdate Updated;
|
|
Updated.bUpdatedBaseColor = UpdatedCaptures.bBaseColor;
|
|
Updated.bUpdatedRoughness = UpdatedCaptures.bRoughness;
|
|
Updated.bUpdatedSpecular = UpdatedCaptures.bSpecular;
|
|
Updated.bUpdatedMetallic = UpdatedCaptures.bMetallic;
|
|
Updated.bUpdatedPackedMRS = UpdatedCaptures.bCombinedMRS;
|
|
Updated.bUpdatedNormalMap = UpdatedCaptures.bWorldNormal;
|
|
Updated.bUpdatedEmissive = UpdatedCaptures.bEmissive;
|
|
Updated.bUpdatedOpacity = UpdatedCaptures.bOpacity;
|
|
Updated.bUpdatedDeviceDepth = UpdatedCaptures.bDeviceDepth;
|
|
Updated.bUpdatedSubsurfaceColor = UpdatedCaptures.bSubsurfaceColor;
|
|
|
|
return Updated;
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
|
|
// Return the SceneCapture configuration, the Flags in the returned struct are set if the corresponding capture type is computed
|
|
FSceneCaptureConfig UE::Geometry::GetSceneCaptureConfig(
|
|
const FSceneCapturePhotoSet& SceneCapture,
|
|
const FSceneCapturePhotoSet::ECaptureTypeStatus QueryStatus)
|
|
{
|
|
FSceneCaptureConfig Result;
|
|
|
|
ForEachCaptureType([&SceneCapture, &QueryStatus, &Result](ERenderCaptureType CaptureType)
|
|
{
|
|
Result.Flags[CaptureType] = (SceneCapture.GetCaptureTypeStatus(CaptureType) == QueryStatus);
|
|
});
|
|
|
|
const TArray<FSpatialPhotoParams> SpatialParams = SceneCapture.GetSpatialPhotoParams();
|
|
|
|
if (!SpatialParams.IsEmpty())
|
|
{
|
|
ensure(SpatialParams[0].Dimensions.IsSquare());
|
|
Result.RenderCaptureImageSize = SpatialParams[0].Dimensions.GetWidth();
|
|
Result.FieldOfViewDegrees = SpatialParams[0].HorzFOVDegrees;
|
|
Result.NearPlaneDist = SpatialParams[0].NearPlaneDist;
|
|
|
|
const bool Values[] = {
|
|
SceneCapture.GetCaptureConfig(ERenderCaptureType::BaseColor).bAntiAliasing,
|
|
SceneCapture.GetCaptureConfig(ERenderCaptureType::WorldNormal).bAntiAliasing,
|
|
SceneCapture.GetCaptureConfig(ERenderCaptureType::Metallic).bAntiAliasing,
|
|
SceneCapture.GetCaptureConfig(ERenderCaptureType::Roughness).bAntiAliasing,
|
|
SceneCapture.GetCaptureConfig(ERenderCaptureType::Specular).bAntiAliasing,
|
|
SceneCapture.GetCaptureConfig(ERenderCaptureType::CombinedMRS).bAntiAliasing,
|
|
SceneCapture.GetCaptureConfig(ERenderCaptureType::Emissive).bAntiAliasing,
|
|
SceneCapture.GetCaptureConfig(ERenderCaptureType::Opacity).bAntiAliasing,
|
|
SceneCapture.GetCaptureConfig(ERenderCaptureType::SubsurfaceColor).bAntiAliasing,
|
|
SceneCapture.GetCaptureConfig(ERenderCaptureType::DeviceDepth).bAntiAliasing,
|
|
};
|
|
Result.bAntiAliasing = Algo::AnyOf(Values);
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
FRenderCaptureOptions UE::Geometry::GetComputedPhotoSetOptions(const TUniquePtr<FSceneCapturePhotoSet>& SceneCapture)
|
|
{
|
|
FSceneCaptureConfig Config = GetSceneCaptureConfig(SceneCapture);
|
|
|
|
FRenderCaptureOptions Options;
|
|
|
|
Options.RenderCaptureImageSize = Config.RenderCaptureImageSize;
|
|
Options.bAntiAliasing = Config.bAntiAliasing;
|
|
Options.FieldOfViewDegrees = Config.FieldOfViewDegrees;
|
|
Options.NearPlaneDist = Config.NearPlaneDist;
|
|
Options.bBakeBaseColor = Config.Flags.bBaseColor;
|
|
Options.bBakeRoughness = Config.Flags.bRoughness;
|
|
Options.bBakeMetallic = Config.Flags.bMetallic;
|
|
Options.bBakeSpecular = Config.Flags.bSpecular;
|
|
Options.bBakeEmissive = Config.Flags.bEmissive;
|
|
Options.bBakeNormalMap = Config.Flags.bWorldNormal;
|
|
Options.bBakeOpacity = Config.Flags.bOpacity;
|
|
Options.bBakeSubsurfaceColor = Config.Flags.bSubsurfaceColor;
|
|
Options.bBakeDeviceDepth = Config.Flags.bDeviceDepth;
|
|
Options.bUsePackedMRS = Config.Flags.bCombinedMRS;
|
|
|
|
return Options;
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
|
|
|
|
|
|
template <ERenderCaptureType CaptureType>
|
|
TSharedPtr<FRenderCaptureMapEvaluator<FVector4f>>
|
|
MakeColorEvaluator(
|
|
const FSceneCapturePhotoSet::FSceneSample& DefaultSample,
|
|
const FSceneCapturePhotoSet* SceneCapture)
|
|
{
|
|
// Bake will crash if we haven't computed the photo sets corresponding to the CaptureType
|
|
check(SceneCapture->GetCaptureTypeStatus(CaptureType) == FSceneCapturePhotoSet::ECaptureTypeStatus::Computed);
|
|
|
|
TSharedPtr<FRenderCaptureMapEvaluator<FVector4f>> Evaluator = MakeShared<FRenderCaptureMapEvaluator<FVector4f>>();
|
|
|
|
switch (CaptureType) {
|
|
case ERenderCaptureType::BaseColor:
|
|
Evaluator->Channel = ERenderCaptureChannel::BaseColor;
|
|
break;
|
|
case ERenderCaptureType::Roughness:
|
|
Evaluator->Channel = ERenderCaptureChannel::Roughness;
|
|
break;
|
|
case ERenderCaptureType::Metallic:
|
|
Evaluator->Channel = ERenderCaptureChannel::Metallic;
|
|
break;
|
|
case ERenderCaptureType::Specular:
|
|
Evaluator->Channel = ERenderCaptureChannel::Specular;
|
|
break;
|
|
case ERenderCaptureType::Emissive:
|
|
Evaluator->Channel = ERenderCaptureChannel::Emissive;
|
|
break;
|
|
case ERenderCaptureType::WorldNormal:
|
|
Evaluator->Channel = ERenderCaptureChannel::WorldNormal;
|
|
break;
|
|
case ERenderCaptureType::CombinedMRS:
|
|
Evaluator->Channel = ERenderCaptureChannel::CombinedMRS;
|
|
break;
|
|
case ERenderCaptureType::Opacity:
|
|
Evaluator->Channel = ERenderCaptureChannel::Opacity;
|
|
break;
|
|
case ERenderCaptureType::SubsurfaceColor:
|
|
Evaluator->Channel = ERenderCaptureChannel::SubsurfaceColor;
|
|
break;
|
|
case ERenderCaptureType::DeviceDepth:
|
|
Evaluator->Channel = ERenderCaptureChannel::DeviceDepth;
|
|
break;
|
|
}
|
|
|
|
Evaluator->DefaultResult = DefaultSample.GetValue4f(CaptureType);
|
|
|
|
Evaluator->EvaluateSampleCallback = [DefaultSample, SceneCapture](const FMeshMapEvaluator::FCorrespondenceSample& Sample)
|
|
{
|
|
const int PhotoIndex = Sample.DetailTriID;
|
|
const FVector2d PhotoCoords(Sample.DetailBaryCoords.X, Sample.DetailBaryCoords.Y);
|
|
const FVector4f SampleColor = SceneCapture->ComputeSampleNearest<CaptureType>(PhotoIndex, PhotoCoords, DefaultSample);
|
|
return SampleColor;
|
|
};
|
|
|
|
Evaluator->EvaluateColorCallback = [](const int DataIdx, float*& In)
|
|
{
|
|
const FVector4f Out(In[0], In[1], In[2], In[3]);
|
|
In += 4;
|
|
return Out;
|
|
};
|
|
|
|
return Evaluator;
|
|
}
|
|
|
|
|
|
TUniquePtr<FMeshMapBaker> UE::Geometry::MakeRenderCaptureBaker(
|
|
FDynamicMesh3* BaseMesh,
|
|
TSharedPtr<UE::Geometry::FMeshTangentsd, ESPMode::ThreadSafe> BaseMeshTangents,
|
|
TSharedPtr<TArray<int32>, ESPMode::ThreadSafe> BaseMeshUVCharts,
|
|
FSceneCapturePhotoSet* SceneCapture,
|
|
FSceneCapturePhotoSetSampler* Sampler,
|
|
FRenderCaptureTypeFlags PendingBake,
|
|
int32 TargetUVLayer,
|
|
EBakeTextureResolution TextureImageSize,
|
|
EBakeTextureSamplesPerPixel SamplesPerPixel,
|
|
FRenderCaptureOcclusionHandler* OcclusionHandler)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(MakeRenderCaptureBaker);
|
|
|
|
check(BaseMesh != nullptr);
|
|
check(BaseMeshTangents.IsValid());
|
|
check(BaseMeshUVCharts.IsValid());
|
|
check(SceneCapture != nullptr);
|
|
check(Sampler != nullptr);
|
|
check(OcclusionHandler != nullptr);
|
|
|
|
auto RegisterSampleStats = [OcclusionHandler](bool bSampleValid, const FMeshMapEvaluator::FCorrespondenceSample& Sample, const FVector2d& UVPosition, const FVector2i& ImageCoords)
|
|
{
|
|
OcclusionHandler->RegisterSample(ImageCoords, bSampleValid);
|
|
};
|
|
|
|
auto ComputeAndApplyInfill = [OcclusionHandler](TArray<TUniquePtr<TImageBuilder<FVector4f>>>& BakeResults)
|
|
{
|
|
OcclusionHandler->ComputeAndApplyInfill(BakeResults);
|
|
};
|
|
|
|
TUniquePtr<FMeshMapBaker> Result = MakeUnique<FMeshMapBaker>();
|
|
Result->SetTargetMesh(BaseMesh);
|
|
Result->SetTargetMeshTangents(BaseMeshTangents);
|
|
Result->SetTargetMeshUVCharts(BaseMeshUVCharts.Get());
|
|
Result->SetDimensions(FImageDimensions(static_cast<int32>(TextureImageSize), static_cast<int32>(TextureImageSize)));
|
|
Result->SetSamplesPerPixel(static_cast<int32>(SamplesPerPixel));
|
|
Result->SetFilter(FMeshMapBaker::EBakeFilterType::BSpline);
|
|
Result->SetTargetMeshUVLayer(TargetUVLayer);
|
|
Result->InteriorSampleCallback = RegisterSampleStats;
|
|
Result->PostWriteToImageCallback = ComputeAndApplyInfill;
|
|
Result->SetDetailSampler(Sampler);
|
|
Result->SetCorrespondenceStrategy(FMeshMapBaker::ECorrespondenceStrategy::Custom);
|
|
|
|
// Pixels in the output textures which don't map onto the mesh have a light grey color (except the normal map which
|
|
// will show a color corresponding to a unit z tangent space normal)
|
|
const FVector4f InvalidColor(.42, .42, .42, 1);
|
|
const FVector3f DefaultNormal = FVector3f::UnitZ();
|
|
|
|
FSceneCapturePhotoSet::FSceneSample DefaultColorSample;
|
|
DefaultColorSample.BaseColor = FVector3f(InvalidColor.X, InvalidColor.Y, InvalidColor.Z);
|
|
DefaultColorSample.Roughness = InvalidColor.X;
|
|
DefaultColorSample.Specular = InvalidColor.X;
|
|
DefaultColorSample.Metallic = InvalidColor.X;
|
|
DefaultColorSample.Emissive = FVector3f(InvalidColor.X, InvalidColor.Y, InvalidColor.Z);
|
|
DefaultColorSample.Opacity = InvalidColor.X;
|
|
DefaultColorSample.SubsurfaceColor = FVector3f(InvalidColor.X, InvalidColor.Y, InvalidColor.Z);
|
|
DefaultColorSample.WorldNormal = FVector4f((DefaultNormal + FVector3f::One()) * .5f, InvalidColor.W);
|
|
DefaultColorSample.DeviceDepth = 0; // We use 0 here since this corresponds to the infinite far plane value. Also, we don't preview the depth texture
|
|
|
|
auto AddColorEvaluator = [&Result, OcclusionHandler] (const TSharedPtr<FRenderCaptureMapEvaluator<FVector4f>>& Evaluator)
|
|
{
|
|
Result->AddEvaluator(Evaluator);
|
|
OcclusionHandler->PushInfillRequired(true);
|
|
};
|
|
|
|
if (PendingBake.bDeviceDepth)
|
|
{
|
|
AddColorEvaluator(MakeColorEvaluator<ERenderCaptureType::DeviceDepth>(DefaultColorSample, SceneCapture));
|
|
}
|
|
if (PendingBake.bBaseColor)
|
|
{
|
|
AddColorEvaluator(MakeColorEvaluator<ERenderCaptureType::BaseColor>(DefaultColorSample, SceneCapture));
|
|
}
|
|
if (PendingBake.bCombinedMRS)
|
|
{
|
|
AddColorEvaluator(MakeColorEvaluator<ERenderCaptureType::CombinedMRS>(DefaultColorSample, SceneCapture));
|
|
}
|
|
if (PendingBake.bRoughness)
|
|
{
|
|
AddColorEvaluator(MakeColorEvaluator<ERenderCaptureType::Roughness>(DefaultColorSample, SceneCapture));
|
|
}
|
|
if (PendingBake.bMetallic)
|
|
{
|
|
AddColorEvaluator(MakeColorEvaluator<ERenderCaptureType::Metallic>(DefaultColorSample, SceneCapture));
|
|
}
|
|
if (PendingBake.bSpecular)
|
|
{
|
|
AddColorEvaluator(MakeColorEvaluator<ERenderCaptureType::Specular>(DefaultColorSample, SceneCapture));
|
|
}
|
|
if (PendingBake.bEmissive)
|
|
{
|
|
AddColorEvaluator(MakeColorEvaluator<ERenderCaptureType::Emissive>(DefaultColorSample, SceneCapture));
|
|
}
|
|
if (PendingBake.bOpacity)
|
|
{
|
|
AddColorEvaluator(MakeColorEvaluator<ERenderCaptureType::Opacity>(DefaultColorSample, SceneCapture));
|
|
}
|
|
if (PendingBake.bSubsurfaceColor)
|
|
{
|
|
AddColorEvaluator(MakeColorEvaluator<ERenderCaptureType::SubsurfaceColor>(DefaultColorSample, SceneCapture));
|
|
}
|
|
if (PendingBake.bWorldNormal)
|
|
{
|
|
// Bake will crash if we haven't computed the WorldNormal photo sets
|
|
check(SceneCapture->GetCaptureTypeStatus(ERenderCaptureType::WorldNormal) == FSceneCapturePhotoSet::ECaptureTypeStatus::Computed);
|
|
|
|
TSharedPtr<FRenderCaptureMapEvaluator<FVector3f>> Evaluator = MakeShared<FRenderCaptureMapEvaluator<FVector3f>>();
|
|
|
|
Evaluator->Channel = ERenderCaptureChannel::WorldNormal;
|
|
|
|
Evaluator->DefaultResult = DefaultNormal;
|
|
|
|
Evaluator->EvaluateSampleCallback = [SceneCapture, BaseMeshTangents, DefaultColorSample](const FMeshMapEvaluator::FCorrespondenceSample& Sample)
|
|
{
|
|
const int32 TriangleID = Sample.BaseSample.TriangleIndex;
|
|
const FVector3d BaryCoords = Sample.BaseSample.BaryCoords;
|
|
const int PhotoIndex = Sample.DetailTriID;
|
|
const FVector2d PhotoCoords(Sample.DetailBaryCoords.X, Sample.DetailBaryCoords.Y);
|
|
|
|
const FVector4f NormalColor = SceneCapture->ComputeSampleNearest<ERenderCaptureType::WorldNormal>(PhotoIndex, PhotoCoords, DefaultColorSample);
|
|
|
|
// Map from color components [0,1] to normal components [-1,1]
|
|
const FVector3f WorldSpaceNormal(
|
|
(NormalColor.X - 0.5f) * 2.0f,
|
|
(NormalColor.Y - 0.5f) * 2.0f,
|
|
(NormalColor.Z - 0.5f) * 2.0f);
|
|
|
|
// Get tangents on base mesh
|
|
FVector3d BaseTangentX, BaseTangentY;
|
|
BaseMeshTangents->GetInterpolatedTriangleTangent(TriangleID, BaryCoords, BaseTangentX, BaseTangentY);
|
|
|
|
// Compute normal in tangent space
|
|
const FVector3f TangentSpaceNormal(
|
|
(float)WorldSpaceNormal.Dot(FVector3f(BaseTangentX)),
|
|
(float)WorldSpaceNormal.Dot(FVector3f(BaseTangentY)),
|
|
(float)WorldSpaceNormal.Dot(FVector3f(Sample.BaseNormal)));
|
|
|
|
return TangentSpaceNormal;
|
|
};
|
|
|
|
Evaluator->EvaluateColorCallback = [](const int DataIdx, float*& In)
|
|
{
|
|
// Map normal space [-1,1] to color space [0,1]
|
|
const FVector3f Normal(In[0], In[1], In[2]);
|
|
const FVector3f Color = (Normal + FVector3f::One()) * 0.5f;
|
|
const FVector4f Out(Color.X, Color.Y, Color.Z, 1.0f);
|
|
In += 3;
|
|
return Out;
|
|
};
|
|
|
|
Result->AddEvaluator(Evaluator);
|
|
|
|
// Note: No infill on normal map for now, doesn't make sense to do after mapping to tangent space!
|
|
// (should we build baked normal map in world space, and then resample to tangent space??)
|
|
OcclusionHandler->PushInfillRequired(false);
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
TUniquePtr<FMeshMapBaker> UE::Geometry::MakeRenderCaptureBaker(
|
|
FDynamicMesh3* BaseMesh,
|
|
TSharedPtr<UE::Geometry::FMeshTangentsd, ESPMode::ThreadSafe> BaseMeshTangents,
|
|
TSharedPtr<TArray<int32>, ESPMode::ThreadSafe> BaseMeshUVCharts,
|
|
FSceneCapturePhotoSet* SceneCapture,
|
|
FSceneCapturePhotoSetSampler* Sampler,
|
|
FRenderCaptureOptions Options,
|
|
int32 TargetUVLayer,
|
|
EBakeTextureResolution TextureImageSize,
|
|
EBakeTextureSamplesPerPixel SamplesPerPixel,
|
|
FRenderCaptureOcclusionHandler* OcclusionHandler)
|
|
{
|
|
FRenderCaptureTypeFlags Flags;
|
|
|
|
Flags.bBaseColor = Options.bBakeBaseColor;
|
|
Flags.bRoughness = Options.bBakeRoughness;
|
|
Flags.bMetallic = Options.bBakeMetallic;
|
|
Flags.bSpecular = Options.bBakeSpecular;
|
|
Flags.bEmissive = Options.bBakeEmissive;
|
|
Flags.bWorldNormal = Options.bBakeNormalMap;
|
|
Flags.bOpacity = Options.bBakeOpacity;
|
|
Flags.bSubsurfaceColor = Options.bBakeSubsurfaceColor;
|
|
Flags.bDeviceDepth = Options.bBakeDeviceDepth;
|
|
Flags.bCombinedMRS = Options.bUsePackedMRS;
|
|
|
|
return MakeRenderCaptureBaker(
|
|
BaseMesh,
|
|
BaseMeshTangents,
|
|
BaseMeshUVCharts,
|
|
SceneCapture,
|
|
Sampler,
|
|
Flags,
|
|
TargetUVLayer,
|
|
TextureImageSize,
|
|
SamplesPerPixel,
|
|
OcclusionHandler);
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
|
|
|
|
void UE::Geometry::GetTexturesFromRenderCaptureBaker(FMeshMapBaker& Baker, FRenderCaptureTextures& TexturesOut)
|
|
{
|
|
// We do this to defer work I guess, it was like this in the original ApproximateActors implementation :DeferredPopulateSourceData
|
|
constexpr bool bPopulateSourceData = false;
|
|
|
|
const int32 NumEval = Baker.NumEvaluators();
|
|
for (int32 EvalIdx = 0; EvalIdx < NumEval; ++EvalIdx)
|
|
{
|
|
FMeshMapEvaluator* BaseEval = Baker.GetEvaluator(EvalIdx);
|
|
check(BaseEval->DataLayout().Num() == 1);
|
|
switch (BaseEval->DataLayout()[0])
|
|
{
|
|
case FMeshMapEvaluator::EComponents::Float4:
|
|
|
|
{
|
|
FRenderCaptureMapEvaluator<FVector4f>* Eval = static_cast<FRenderCaptureMapEvaluator<FVector4f>*>(BaseEval);
|
|
TUniquePtr<TImageBuilder<FVector4f>> ImageBuilder = MoveTemp(Baker.GetBakeResults(EvalIdx)[0]);
|
|
|
|
if (ensure(ImageBuilder.IsValid()) == false) return;
|
|
|
|
switch (Eval->Channel)
|
|
{
|
|
case ERenderCaptureChannel::BaseColor:
|
|
|
|
TexturesOut.BaseColorMap = FTexture2DBuilder::BuildTextureFromImage(
|
|
*ImageBuilder,
|
|
FTexture2DBuilder::ETextureType::Color,
|
|
true,
|
|
bPopulateSourceData);
|
|
break;
|
|
|
|
case ERenderCaptureChannel::SubsurfaceColor:
|
|
|
|
// The SubsurfaceColor GBuffer channel is gamma encoded so:
|
|
// 1. Dont convert the data in ImageBuilder to sRGB, it is already gamma encoded
|
|
// 2. Pass the ETextureType::Color enum so the built UTexture2D will have SRGB checked
|
|
TexturesOut.SubsurfaceColorMap = FTexture2DBuilder::BuildTextureFromImage(
|
|
*ImageBuilder,
|
|
FTexture2DBuilder::ETextureType::Color,
|
|
false,
|
|
bPopulateSourceData);
|
|
break;
|
|
|
|
case ERenderCaptureChannel::Opacity:
|
|
|
|
TexturesOut.OpacityMap = FTexture2DBuilder::BuildTextureFromImage(
|
|
*ImageBuilder,
|
|
FTexture2DBuilder::ETextureType::ColorLinear,
|
|
false,
|
|
bPopulateSourceData);
|
|
break;
|
|
|
|
case ERenderCaptureChannel::Roughness:
|
|
|
|
TexturesOut.RoughnessMap = FTexture2DBuilder::BuildTextureFromImage(
|
|
*ImageBuilder,
|
|
FTexture2DBuilder::ETextureType::Roughness,
|
|
false,
|
|
bPopulateSourceData);
|
|
break;
|
|
|
|
case ERenderCaptureChannel::Metallic:
|
|
|
|
TexturesOut.MetallicMap = FTexture2DBuilder::BuildTextureFromImage(
|
|
*ImageBuilder,
|
|
FTexture2DBuilder::ETextureType::Metallic,
|
|
false,
|
|
bPopulateSourceData);
|
|
break;
|
|
|
|
case ERenderCaptureChannel::Specular:
|
|
|
|
TexturesOut.SpecularMap = FTexture2DBuilder::BuildTextureFromImage(
|
|
*ImageBuilder,
|
|
FTexture2DBuilder::ETextureType::Specular,
|
|
false,
|
|
bPopulateSourceData);
|
|
break;
|
|
|
|
case ERenderCaptureChannel::Emissive:
|
|
|
|
TexturesOut.EmissiveMap = FTexture2DBuilder::BuildTextureFromImage(
|
|
*ImageBuilder,
|
|
FTexture2DBuilder::ETextureType::EmissiveHDR,
|
|
false,
|
|
bPopulateSourceData);
|
|
TexturesOut.EmissiveMap->CompressionSettings = TC_HDR_Compressed;
|
|
break;
|
|
|
|
case ERenderCaptureChannel::CombinedMRS:
|
|
|
|
TexturesOut.PackedMRSMap = FTexture2DBuilder::BuildTextureFromImage(
|
|
*ImageBuilder,
|
|
FTexture2DBuilder::ETextureType::ColorLinear,
|
|
false,
|
|
bPopulateSourceData);
|
|
break;
|
|
|
|
case ERenderCaptureChannel::DeviceDepth:
|
|
|
|
// Add a null pointer for this evaluator, the depth capture is used internally and not presented to the user
|
|
// TODO Implemented this, it could be useful for applications outside BakeRC
|
|
break;
|
|
|
|
case ERenderCaptureChannel::WorldNormal:
|
|
|
|
// This should be handled in the Float3 branch
|
|
ensure(false);
|
|
return;
|
|
|
|
default:
|
|
ensure(false);
|
|
return;
|
|
}
|
|
|
|
} break; // Float4
|
|
|
|
case FMeshMapEvaluator::EComponents::Float3:
|
|
|
|
{
|
|
FRenderCaptureMapEvaluator<FVector3f>* Eval = static_cast<FRenderCaptureMapEvaluator<FVector3f>*>(BaseEval);
|
|
TUniquePtr<TImageBuilder<FVector4f>> ImageBuilder = MoveTemp(Baker.GetBakeResults(EvalIdx)[0]);
|
|
|
|
if (ensure(ImageBuilder.IsValid()) == false) return;
|
|
if (ensure(Eval->Channel == ERenderCaptureChannel::WorldNormal) == false) return;
|
|
|
|
TexturesOut.NormalMap = FTexture2DBuilder::BuildTextureFromImage(
|
|
*ImageBuilder,
|
|
FTexture2DBuilder::ETextureType::NormalMap,
|
|
false,
|
|
bPopulateSourceData);
|
|
|
|
} break; // Float3
|
|
|
|
default:
|
|
ensure(false);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Everything that follows is deprecated
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void UE::Geometry::ConfigureSceneCapture(
|
|
const TUniquePtr<FSceneCapturePhotoSet>& SceneCapture,
|
|
const TArray<TObjectPtr<AActor>>& Actors,
|
|
const FSceneCaptureConfig& Options,
|
|
bool bAllowCancel)
|
|
{
|
|
return ConfigureSceneCapture(*SceneCapture, Actors, Options, bAllowCancel);
|
|
}
|
|
|
|
UE::Geometry::FRenderCaptureTypeFlags UE::Geometry::UpdateSceneCapture(
|
|
const TUniquePtr<FSceneCapturePhotoSet>& SceneCapture,
|
|
const TArray<TObjectPtr<AActor>>& Actors,
|
|
const FSceneCaptureConfig& DesiredConfig,
|
|
bool bAllowCancel)
|
|
{
|
|
return UpdateSceneCapture(*SceneCapture, Actors, DesiredConfig, bAllowCancel);
|
|
}
|
|
|
|
FSceneCaptureConfig UE::Geometry::GetSceneCaptureConfig(
|
|
const TUniquePtr<FSceneCapturePhotoSet>& SceneCapture,
|
|
const FSceneCapturePhotoSet::ECaptureTypeStatus QueryStatus)
|
|
{
|
|
return GetSceneCaptureConfig(*SceneCapture, QueryStatus);
|
|
}
|
|
|
|
void UE::Geometry::GetTexturesFromRenderCaptureBaker(const TUniquePtr<FMeshMapBaker>& Baker, FRenderCaptureTextures& TexturesOut)
|
|
{
|
|
GetTexturesFromRenderCaptureBaker(*Baker, TexturesOut);
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|