You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
* Fixed a crash that occurred in the baking step when cancelling a scene capture which was triggered by changing an option which invalidated all photo sets (i.e., render capture resolution or capture anti aliasing). This was occurring because the baking step proceeded after the scene capture cancellation and the needed photosets were empty. * Fixed a crash which can happen if you have no channel enabled, change cleanup tolerance from 0 to 1 and then enable the BaseColor channel. This was fixed by changing the behavior of the Cleanup Threshold property, if its positive the DeviceDepth capture is enabled and its disabled otherwise, previously we also required that another channel was enabled which led to the crash in the baking step. The new semantics are easier to understand in the code and and is probably an improvement to the UX as well since the Cleanup Threshold property behavior does not depend on other properties. * Improved the scene capture cancellation: Users can now cancel the scene capture when the tool is starting up which is better UX since if they picked slow settings they don't intend to reuse, the capture can be cancelled, also, the code is simpler. Changed the behavior of the BakeRC tool properties so that after cancellation the completed captures with the desired settings are enabled. The old behavior where cancellation was documented to restore old results was broken and cannot be made to work unless we store the old results in a separate scene capture but for high capture resolutions that would require a lot of memory. When the SceneCapture is cancelled the channels which were pending and didn't complete are logged in the Output Log in case the user forgets which ones were enabled. * Simplified the code in various places: Added `operator[](ERenderCaptureType)` to structs with per-capture data, and use it to simplify many places where we previously checked channels individually. Added conversion functions from tool properties/geometry script parameters to the render capture parameters. Removed old confusing logic that determined if a capture had been updated by tracking the number of photos in each photoset with a new system based on a new per-capture pending state. Deprecated a bunch of functions which are no longer useful or which used the wrong types (e.g., `FRenderCaptureUpdate` was added because I hadn't noticed `FRenderCaptureTypeFlags`). Changed the defaults in `FSceneCapturePhotoSet` so that the DeviceDepth capture is off, its an advanced feature and the defaults are intended to make the class friendly to new developers #rb lonnie.li [CL 26568190 by matija kecman in 5.3 branch]
851 lines
30 KiB
C++
851 lines
30 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(
|
|
const TUniquePtr<FSceneCapturePhotoSet>& SceneCapture,
|
|
const TArray<TObjectPtr<AActor>>& Actors,
|
|
const FSceneCaptureConfig& Options,
|
|
bool bAllowCancel)
|
|
{
|
|
if (SceneCapture.IsValid() == false)
|
|
{
|
|
ensure(false);
|
|
return;
|
|
}
|
|
|
|
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,
|
|
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(
|
|
const TUniquePtr<FSceneCapturePhotoSet>& SceneCapture,
|
|
const TArray<TObjectPtr<AActor>>& Actors,
|
|
const FSceneCaptureConfig& DesiredConfig,
|
|
bool bAllowCancel)
|
|
{
|
|
check(SceneCapture.IsValid());
|
|
|
|
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 TUniquePtr<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(const TUniquePtr<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;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#undef LOCTEXT_NAMESPACE
|