ModelingTools: Fix texture filtering into neighboring charts for tightly packed UV shells.

#rb jimmy.andrews michael.balzer
#rnx
#jira none
#preflight 61bbb2868a62de8385c38473

#ROBOMERGE-AUTHOR: lonnie.li
#ROBOMERGE-SOURCE: CL 18480102 in //UE5/Release-5.0/... via CL 18481553
#ROBOMERGE-BOT: STARSHIP (Release-Engine-Staging -> Release-Engine-Test) (v899-18417669)

[CL 18481825 by lonnie li in ue5-release-engine-test branch]
This commit is contained in:
lonnie li
2021-12-16 19:57:29 -05:00
parent 16fbde348f
commit 8e197859df
7 changed files with 136 additions and 18 deletions
@@ -5,7 +5,7 @@
#include "Sampling/MeshMapBakerQueue.h"
#include "Image/ImageOccupancyMap.h"
#include "Image/ImageTile.h"
#include "Algo/Count.h"
#include "Selections/MeshConnectedComponents.h"
#include "ProfilingDebugging/ScopedTimers.h"
using namespace UE::Geometry;
@@ -81,6 +81,13 @@ void FMeshMapBaker::InitBake()
}
InitFilter();
// Compute UV charts if null or invalid.
if (!TargetMeshUVCharts || !ensure(TargetMeshUVCharts->Num() == TargetMesh->TriangleCount()))
{
ComputeUVCharts(*TargetMesh, TargetMeshUVChartsLocal);
TargetMeshUVCharts = &TargetMeshUVChartsLocal;
}
}
void FMeshMapBaker::InitBakeDefaults()
@@ -121,7 +128,7 @@ void FMeshMapBaker::Bake()
BakeAnalytics.Reset();
FScopedDurationTimer TotalBakeTimer(BakeAnalytics.TotalBakeDuration);
if (Bakers.IsEmpty())
if (Bakers.IsEmpty() || !TargetMesh)
{
return;
}
@@ -305,11 +312,17 @@ void FMeshMapBaker::Bake()
FImageOccupancyMap OccupancyMap;
OccupancyMap.GutterSize = GutterSize;
OccupancyMap.Initialize(Dimensions, Tile, SamplesPerPixel);
OccupancyMap.ComputeFromUVSpaceMesh(FlatMesh, [this](int32 TriangleID) { return FlatMesh.GetTriangleGroup(TriangleID); });
OccupancyMap.Initialize(Dimensions, PaddedTile, SamplesPerPixel);
OccupancyMap.ComputeFromUVSpaceMesh(FlatMesh, [this](int32 TriangleID) { return FlatMesh.GetTriangleGroup(TriangleID); }, TargetMeshUVCharts);
GutterTexelsPerTile[TileIdx] = OccupancyMap.GutterTexels;
BakeAnalytics.NumSamplePixels += Algo::CountIf(OccupancyMap.TexelInteriorSamples, [](const int32 Value){ return Value > 0; });
const int64 NumTilePixels = Tile.Num();
for (int64 TilePixelIdx = 0; TilePixelIdx < NumTilePixels; ++TilePixelIdx)
{
const FVector2i SourceCoords = Tile.GetSourceCoords(TilePixelIdx);
const int64 OccupancyMapIdx = OccupancyMap.Tile.GetIndexFromSourceCoords(SourceCoords);
BakeAnalytics.NumSamplePixels += OccupancyMap.TexelInteriorSamples[OccupancyMapIdx];;
}
FMeshMapTileBuffer* TileBuffer = new FMeshMapTileBuffer(PaddedTile, BakeSampleBufferSize);
@@ -330,16 +343,16 @@ void FMeshMapBaker::Bake()
return;
}
const int64 TilePixelLinearIdx = Tile.GetIndex(TileCoords);
if (OccupancyMap.TexelNumSamples(TilePixelLinearIdx) == 0)
const FVector2i ImageCoords = Tile.GetSourceCoords(TileCoords);
const int64 OccupancyMapLinearIdx = OccupancyMap.Tile.GetIndexFromSourceCoords(ImageCoords);
if (OccupancyMap.TexelNumSamples(OccupancyMapLinearIdx) == 0)
{
continue;
}
const FVector2i ImageCoords = Tile.GetSourceCoords(TileCoords);
for (int32 SampleIdx = 0; SampleIdx < NumSamples; ++SampleIdx)
{
const int64 LinearIdx = TilePixelLinearIdx * NumSamples + SampleIdx;
const int64 LinearIdx = OccupancyMapLinearIdx * NumSamples + SampleIdx;
if (OccupancyMap.IsInterior(LinearIdx))
{
const FVector2d UVPosition = (FVector2d)OccupancyMap.TexelQueryUV[LinearIdx];
@@ -349,7 +362,7 @@ void FMeshMapBaker::Bake()
DetailCorrespondenceSampler.SampleUV(UVTriangleID, UVPosition, Sample);
if (Sample.DetailMesh && DetailSampler->IsTriangle(Sample.DetailMesh, Sample.DetailTriID))
{
BakeSample(*TileBuffer, Sample, UVPosition, ImageCoords);
BakeSample(*TileBuffer, Sample, UVPosition, ImageCoords, OccupancyMap);
}
}
}
@@ -479,7 +492,8 @@ void FMeshMapBaker::BakeSample(
FMeshMapTileBuffer& TileBuffer,
const FMeshMapEvaluator::FCorrespondenceSample& Sample,
const FVector2d& UVPosition,
const FVector2i& ImageCoords)
const FVector2i& ImageCoords,
const FImageOccupancyMap& OccupancyMap)
{
// Evaluate each baker into stack allocated float buffer
float* Buffer = static_cast<float*>(FMemory_Alloca(sizeof(float) * BakeSampleBufferSize));
@@ -493,7 +507,10 @@ void FMeshMapBaker::BakeSample(
const FImageTile& Tile = TileBuffer.GetTile();
auto AddFn = [this, &ImageCoords, &UVPosition, &Tile, &TileBuffer, Buffer](const TArray<int32>& BakeIds) -> void
const int64 OccupancyMapSampleIdx = OccupancyMap.Tile.GetIndexFromSourceCoords(ImageCoords);
const int32 SampleUVChart = OccupancyMap.TexelQueryUVChart[OccupancyMapSampleIdx];
auto AddFn = [this, &ImageCoords, &UVPosition, &Tile, &TileBuffer, Buffer, &OccupancyMap, SampleUVChart](const TArray<int32>& BakeIds) -> void
{
const FVector2i BoxFilterStart(
FMath::Clamp(ImageCoords.X - FilterKernelSize, 0, Dimensions.GetWidth()),
@@ -509,6 +526,8 @@ void FMeshMapBaker::BakeSample(
{
const FVector2i SourceCoords = BoxFilterTile.GetSourceCoords(FilterIdx);
const int64 BufferTilePixelLinearIdx = Tile.GetIndexFromSourceCoords(SourceCoords);
const int64 OccupancyMapFilterIdx = OccupancyMap.Tile.GetIndexFromSourceCoords(SourceCoords);
const int32 BufferTilePixelUVChart = OccupancyMap.TexelQueryUVChart[OccupancyMapFilterIdx];
float* PixelBuffer = TileBuffer.GetPixel(BufferTilePixelLinearIdx);
float& PixelWeight = TileBuffer.GetPixelWeight(BufferTilePixelLinearIdx);
@@ -517,7 +536,8 @@ void FMeshMapBaker::BakeSample(
TexelDistance.X *= Dimensions.GetWidth();
TexelDistance.Y *= Dimensions.GetHeight();
const float FilterWeight = TextureFilterEval(TexelDistance);
float FilterWeight = TextureFilterEval(TexelDistance);
FilterWeight *= (SampleUVChart == BufferTilePixelUVChart);
PixelWeight += FilterWeight;
for (const int32 BakeIdx : BakeIds)
{
@@ -670,4 +690,27 @@ float FMeshMapBaker::EvaluateFilter(const FVector2d& Dist)
}
void FMeshMapBaker::ComputeUVCharts(const FDynamicMesh3& Mesh, TArray<int32>& MeshUVCharts)
{
MeshUVCharts.SetNumZeroed(Mesh.TriangleCount());
if (const FDynamicMeshUVOverlay* UVOverlay = Mesh.Attributes() ? Mesh.Attributes()->PrimaryUV() : nullptr)
{
FMeshConnectedComponents UVComponents(&Mesh);
UVComponents.FindConnectedTriangles();
UVComponents.FindConnectedTriangles([UVOverlay](int32 Triangle0, int32 Triangle1) {
return UVOverlay ? UVOverlay->AreTrianglesConnected(Triangle0, Triangle1) : false;
});
const int32 NumComponents = UVComponents.Num();
for (int32 ComponentId = 0; ComponentId < NumComponents; ++ComponentId)
{
const FMeshConnectedComponents::FComponent& UVComp = UVComponents.GetComponent(ComponentId);
for (const int32 TriId : UVComp.Indices)
{
MeshUVCharts[TriId] = ComponentId;
}
}
}
}
@@ -77,6 +77,36 @@ public:
EBakeFilterType GetFilter() const { return FilterType; }
int32 GetTileSize() const { return TileSize; }
/**
* Computes the connected UV triangles and returns an array containing
* the mapping from triangle ID to unique UV chart ID. If the mesh
* has no UVs, the UVCharts will be initialized to 0.
*
* @param Mesh the mesh to compute UV charts.
* @param MeshUVCharts the triangle ID to UV Chart ID array.
*/
static void ComputeUVCharts(const FDynamicMesh3& Mesh, TArray<int32>& MeshUVCharts);
/**
* Set an a Triangle ID to UV Chart ID array for TargetMesh.
* If this is not set, then the baker will compute it as part of Bake().
* Since ComputeUVCharts() is non-trivial, this method is intended
* to allow a client to externally cache the result of ComputeUVCharts
* to minimize the overhead per bake.
*
* @param UVChartsIn the TriID to UVChartID map
*/
void SetTargetMeshUVCharts(TArray<int32>* UVChartsIn)
{
TargetMeshUVCharts = UVChartsIn;
}
/** @return the Triangle ID to UV Chart ID mapping */
const TArray<int32>* GetTargetMeshUVCharts() const
{
return TargetMeshUVCharts;
}
//
// Analytics
//
@@ -105,7 +135,8 @@ protected:
FMeshMapTileBuffer& TileBuffer,
const FMeshMapEvaluator::FCorrespondenceSample& Sample,
const FVector2d& UVPosition,
const FVector2i& ImageCoords);
const FVector2i& ImageCoords,
const FImageOccupancyMap& OccupancyMap);
/** Initialize evaluation contexts and precompute data for bake evaluation. */
void InitBake();
@@ -184,6 +215,20 @@ protected:
/** Array of bake result images. */
TArray<TUniquePtr<TImageBuilder<FVector4f>>> BakeResults;
/**
* Array of TargetMesh triangle ID to UV chart ID mapping.
* Can be optionally provided by the client. If not provided,
* will be computed as part of the bake.
*/
TArray<int32>* TargetMeshUVCharts = nullptr;
/**
* Local Array of TargetMesh triangle ID to UV chart ID mapping.
* This will be populated only if not provided by the client via
* TargetMeshUVCharts.
*/
TArray<int32> TargetMeshUVChartsLocal;
};
} // end namespace UE::Geometry