Files
FlaxEngine/Source/Engine/ShadowsOfMordor/Builder.Hemispheres.cpp
2024-02-26 19:00:48 +01:00

186 lines
6.8 KiB
C++

// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#include "Builder.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Math/Math.h"
#include "Engine/Level/SceneQuery.h"
#include "Engine/Level/Actors/BoxBrush.h"
#include "Engine/ContentImporters/ImportTexture.h"
namespace
{
void SampleCache(ShadowsOfMordor::GenerateHemispheresData& data, int32 texelX, int32 texelY, Float3& outPosition, Float3& outNormal)
{
const auto mipDataPositions = data.PositionsData.GetData(0, 0);
#if CACHE_POSITIONS_FORMAT == HEMISPHERES_FORMAT_R32G32B32A32
outPosition = Float3(mipDataPositions->Get<Float4>(texelX, texelY));
#elif CACHE_POSITIONS_FORMAT == HEMISPHERES_FORMAT_R16G16B16A16
outPosition = mipDataPositions->Get<Half4>(texelX, texelY).ToFloat3();
#else
#error "Unknown format."
#endif
const auto mipDataNormals = data.NormalsData.GetData(0, 0);
#if CACHE_NORMALS_FORMAT == HEMISPHERES_FORMAT_R32G32B32A32
outNormal = Float3(mipDataNormals->Get<Float4>(texelX, texelY));
#elif CACHE_NORMALS_FORMAT == HEMISPHERES_FORMAT_R16G16B16A16
outNormal = mipDataNormals->Get<Half4>(texelX, texelY).ToFloat3();
#else
#error "Unknown format."
#endif
}
void RejectTexel(ShadowsOfMordor::GenerateHemispheresData& data, int32 texelX, int32 texelY)
{
const auto mipDataNormals = data.NormalsData.GetData(0, 0);
#if CACHE_NORMALS_FORMAT == HEMISPHERES_FORMAT_R32G32B32A32
mipDataNormals->Get<Float4>(texelX, texelY) = Float4::Zero;
#elif CACHE_NORMALS_FORMAT == HEMISPHERES_FORMAT_R16G16B16A16
mipDataNormals->Get<Half4>(texelX, texelY) = Half4::Zero;
#else
#error "Unknown format."
#endif
}
}
void ShadowsOfMordor::Builder::generateHemispheres()
{
reportProgress(BuildProgressStep::GenerateHemispheresCache, 0.0f);
// Clear all lightmaps
_workerStagePosition0 = 0;
if (runStage(CleanLightmaps))
return;
auto scene = _scenes[_workerActiveSceneIndex];
auto lightmapsCount = scene->Lightmaps.Count();
auto& settings = scene->GetSettings();
// Collect Hemispheres render tasks
int32 hemispheresCount = 0, mergedHemispheresCount = 0;
GenerateHemispheresData cacheData;
// Config
float normalizedQuality = Math::Saturate((float)settings.Quality / 100.0f);
float maxMergeRadius = Math::Lerp(5.0f, 1.0f, normalizedQuality) / LightmapTexelsPerWorldUnit;
float normalSimilarityMin = Math::Lerp(0.8f, 0.95f, normalizedQuality);
int32 maxTexelsDistance = static_cast<int32>(Math::Lerp(2.0f, 1.0f, normalizedQuality));
int32 atlasSize = static_cast<int32>(settings.AtlasSize);
// Process every lightmap
for (_workerStagePosition0 = 0; _workerStagePosition0 < lightmapsCount; _workerStagePosition0++)
{
// Prepare
auto& lightmapEntry = scene->Lightmaps[_workerStagePosition0];
lightmapEntry.Hemispheres.Clear();
lightmapEntry.Hemispheres.EnsureCapacity(Math::Square(atlasSize / 2));
Float3 position, normal;
// Fill cache
if (runStage(RenderCache))
return;
if (waitForJobDataSync())
return;
// Post-process cache
if (runStage(PostprocessCache))
return;
// Wait for GPU commands to sync
if (waitForJobDataSync())
return;
if (checkBuildCancelled())
return;
// Download cache to CPU memory from GPU memory
if (_cachePositions->DownloadData(cacheData.PositionsData)
|| _cacheNormals->DownloadData(cacheData.NormalsData))
{
LOG(Fatal, "Cannot download data from the GPU. Target: ShadowsOfMordor::Builder::RenderPositionsAndNormals");
return;
}
if (checkBuildCancelled())
return;
#if DEBUG_EXPORT_CACHE_PREVIEW
// Here we can export cache to drive
exportCachePreview(scene, cacheData, lightmapEntry);
#endif
// For each texel
for (int32 texelX = 0; texelX < atlasSize; texelX++)
{
for (int32 texelY = 0; texelY < atlasSize; texelY++)
{
// Sample cache for current texel
SampleCache(cacheData, texelX, texelY, position, normal);
// Reject 'empty' texels
if (normal.IsZero())
continue;
normal.Normalize();
// Try to merge similar hemispheres (threshold values are controlled by the quality slider)
int32 mergedCount = 1;
Float3 mergedSumPos = position, mergedSumNorm = normal;
for (int32 x = -maxTexelsDistance; x <= maxTexelsDistance; x++)
{
for (int32 y = -maxTexelsDistance; y <= maxTexelsDistance; y++)
{
int32 xx = Math::Clamp(x + texelX, 0, atlasSize - 1);
int32 yy = Math::Clamp(y + texelY, 0, atlasSize - 1);
// Skip current texel
if (xx == texelX && yy == texelY)
continue;
// Sample cache for possible to use texel
Float3 pp, nn;
SampleCache(cacheData, xx, yy, pp, nn);
nn.Normalize();
if (Float3::Distance(position, pp) <= maxMergeRadius
&& Float3::Dot(normal, nn) >= normalSimilarityMin)
{
// Merge them!
mergedCount++;
mergedSumPos += pp;
mergedSumNorm += nn;
// Remove this hemisphere
RejectTexel(cacheData, xx, yy);
}
}
}
if (mergedCount > 1)
mergedHemispheresCount += mergedCount;
// TODO: check if we need to use avg pos and normal?
//position = mergedSumPos / mergedCount;
//normal = mergedSumNorm / mergedCount;
//normal.Normalize();
// Enqueue hemisphere data to perform batched rendering
HemisphereData data;
data.Position = position;
data.Normal = normal;
data.TexelX = texelX;
data.TexelY = texelY;
lightmapEntry.Hemispheres.Add(data);
hemispheresCount++;
}
}
// Progress Point
reportProgress(BuildProgressStep::GenerateHemispheresCache, (float)_workerStagePosition0 / lightmapsCount);
if (checkBuildCancelled())
return;
}
// Update stats
scene->HemispheresCount = hemispheresCount;
scene->MergedHemispheresCount = mergedHemispheresCount;
reportProgress(BuildProgressStep::GenerateHemispheresCache, 1.0f);
}