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

169 lines
6.3 KiB
C++

// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#include "Builder.h"
#include "AtlasChartsPacker.h"
#include "Engine/Level/Scene/SceneLightmapsData.h"
#include "Engine/Core/Math/Math.h"
#include "Engine/Core/Collections/Sorting.h"
#include "Engine/ContentImporters/ImportTexture.h"
#include "Engine/Core/Log.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Level/SceneQuery.h"
#include "Engine/Level/Scene/Lightmap.h"
bool ShadowsOfMordor::Builder::sortCharts(const LightmapUVsChart& a, const LightmapUVsChart& b)
{
// Sort by area
return (b.Width * b.Height) < (a.Width * a.Height);
}
void ShadowsOfMordor::Builder::generateCharts()
{
reportProgress(BuildProgressStep::GenerateLightmapCharts, 0.0f);
auto scene = _scenes[_workerActiveSceneIndex];
auto& settings = scene->GetSettings();
ScopeLock lock(scene->EntriesLocker);
// Generate lightmap UVs charts
const int32 entriesCount = scene->Entries.Count();
scene->Charts.EnsureCapacity(entriesCount);
const int32 MaximumChartSize = (int32)settings.AtlasSize - settings.ChartsPadding * 2;
for (int32 i = 0; i < entriesCount; i++)
{
LightmapUVsChart chart;
chart.Result.TextureIndex = INVALID_INDEX;
GeometryEntry& entry = scene->Entries[i];
entry.ChartIndex = INVALID_INDEX;
// Calculate desired area for the entry's chart (based on object dimensions and settings)
// Reject missing models or too small objects
Float3 size = entry.Box.GetSize();
float dimensionsCoeff = size.AverageArithmetic();
if (size.X <= 1.0f)
dimensionsCoeff = Float2(size.Y, size.Z).AverageArithmetic();
else if (size.Y <= 1.0f)
dimensionsCoeff = Float2(size.X, size.Z).AverageArithmetic();
else if (size.Z <= 1.0f)
dimensionsCoeff = Float2(size.Y, size.X).AverageArithmetic();
const float scale = settings.GlobalObjectsScale * entry.Scale * LightmapTexelsPerWorldUnit * dimensionsCoeff;
if (scale <= ZeroTolerance)
continue;
// Apply lightmap uvs bounding box (in uv space) to reduce waste of lightmap atlas space
chart.Width = Math::Clamp(Math::CeilToInt(scale * entry.UVsBox.GetWidth()), LightmapMinChartSize, MaximumChartSize);
chart.Height = Math::Clamp(Math::CeilToInt(scale * entry.UVsBox.GetHeight()), LightmapMinChartSize, MaximumChartSize);
// Register lightmap atlas chart entry
chart.EntryIndex = i;
scene->Charts.Add(chart);
// Progress Point
reportProgress(BuildProgressStep::GenerateLightmapCharts, static_cast<float>(i) / entriesCount);
}
reportProgress(BuildProgressStep::GenerateLightmapCharts, 1.0f);
}
void ShadowsOfMordor::Builder::packCharts()
{
reportProgress(BuildProgressStep::PackLightmapCharts, 0.0f);
auto scene = _scenes[_workerActiveSceneIndex];
// Pack UV charts into atlases
Array<AtlasChartsPacker*> packers;
if (scene->Charts.HasItems())
{
// Sort charts from the biggest to the smallest
Sorting::QuickSort(scene->Charts.Get(), scene->Charts.Count(), &sortCharts);
reportProgress(BuildProgressStep::PackLightmapCharts, 0.1f);
// Cache charts indices after sorting operation
scene->EntriesLocker.Lock();
for (int32 chartIndex = 0; chartIndex < scene->Charts.Count(); chartIndex++)
{
auto& chart = scene->Charts[chartIndex];
scene->Entries[chart.EntryIndex].ChartIndex = chartIndex;
}
scene->EntriesLocker.Unlock();
reportProgress(BuildProgressStep::PackLightmapCharts, 0.5f);
// Pack all the charts
for (int32 i = 0; i < scene->Charts.Count(); i++)
{
auto chart = &scene->Charts[i];
bool cannotPack = true;
for (int32 j = 0; j < packers.Count(); j++)
{
if (packers[j]->Insert(chart))
{
chart->Result.TextureIndex = j;
cannotPack = false;
break;
}
}
if (cannotPack)
{
auto packer = New<AtlasChartsPacker>(&scene->GetSettings());
auto result = packer->Insert(chart);
ASSERT(result);
chart->Result.TextureIndex = packers.Count();
packers.Add(packer);
}
}
}
const int32 lightmapsCount = scene->LightmapsCount = packers.Count();
LOG(Info, "Scene \'{0}\': building {1} lightmap(s) ({2} chart(s) to bake)...", scene->Scene->GetName(), lightmapsCount, scene->Charts.Count());
packers.ClearDelete();
// Progress Point
reportProgress(BuildProgressStep::PackLightmapCharts, 1.0f);
}
void ShadowsOfMordor::Builder::updateLightmaps()
{
reportProgress(BuildProgressStep::UpdateLightmapsCollection, 0.0f);
auto scene = _scenes[_workerActiveSceneIndex];
auto& settings = scene->GetSettings();
const int32 lightmapsCount = scene->LightmapsCount;
// Update lightmaps collection
scene->Scene->LightmapsData.UpdateLightmapsCollection(lightmapsCount, (int32)settings.AtlasSize);
scene->Lightmaps.Resize(lightmapsCount, false);
for (int32 lightmapIndex = 0; lightmapIndex < lightmapsCount; lightmapIndex++)
{
if (scene->Lightmaps[lightmapIndex].Init(&settings))
return;
}
// Wait for all lightmaps to be ready (after creating new lightmaps assets we need to wait for resources to be prepared)
GPUDevice::Instance->Locker.Lock();
for (int32 lightmapIndex = 0; lightmapIndex < lightmapsCount; lightmapIndex++)
{
Texture* textures[NUM_SH_TARGETS];
scene->Scene->LightmapsData.GetLightmap(lightmapIndex)->GetTextures(textures);
for (int32 textureIndex = 0; textureIndex < NUM_SH_TARGETS; textureIndex++)
{
auto texture = textures[textureIndex];
GPUDevice::Instance->Locker.Unlock();
if (texture == nullptr || texture->WaitForLoaded())
{
LOG(Error, "Lightmap load failed.");
return;
}
GPUDevice::Instance->Locker.Lock();
}
reportProgress(BuildProgressStep::UpdateLightmapsCollection, (float)lightmapIndex / lightmapsCount);
}
GPUDevice::Instance->Locker.Unlock();
reportProgress(BuildProgressStep::UpdateLightmapsCollection, 1.0f);
}