Split level mesh surface from lightmap tiles as we have multiple surfaces per tile

This commit is contained in:
Magnus Norddahl 2023-12-17 14:17:22 +01:00
commit 48b6a7ec46
19 changed files with 446 additions and 597 deletions

View file

@ -55,11 +55,11 @@ LevelMeshSurface* LevelMesh::Trace(const FVector3& start, FVector3 direction, fl
return hitSurface; // I hit something
}
LevelMeshSurfaceStats LevelMesh::GatherSurfacePixelStats()
LevelMeshTileStats LevelMesh::GatherTilePixelStats()
{
LevelMeshSurfaceStats stats;
StaticMesh->GatherSurfacePixelStats(stats);
DynamicMesh->GatherSurfacePixelStats(stats);
LevelMeshTileStats stats;
StaticMesh->GatherTilePixelStats(stats);
DynamicMesh->GatherTilePixelStats(stats);
return stats;
}
@ -94,98 +94,96 @@ void LevelSubmesh::UpdateCollision()
Collision = std::make_unique<TriangleMeshShape>(Mesh.Vertices.Data(), Mesh.Vertices.Size(), Mesh.Indexes.Data(), Mesh.Indexes.Size());
}
void LevelSubmesh::GatherSurfacePixelStats(LevelMeshSurfaceStats& stats)
void LevelSubmesh::GatherTilePixelStats(LevelMeshTileStats& stats)
{
int count = GetSurfaceCount();
for (int i = 0; i < count; ++i)
for (const LightmapTile& tile : LightmapTiles)
{
const auto* surface = GetSurface(i);
auto area = surface->AtlasTile.Area();
auto area = tile.AtlasLocation.Area();
stats.pixels.total += area;
if (surface->NeedsUpdate)
if (tile.NeedsUpdate)
{
stats.surfaces.dirty++;
stats.tiles.dirty++;
stats.pixels.dirty += area;
}
if (surface->IsSky)
{
stats.surfaces.sky++;
stats.pixels.sky += area;
}
}
stats.surfaces.total += count;
stats.tiles.total += LightmapTiles.Size();
}
struct LevelMeshPlaneGroup
{
FVector4 plane = FVector4(0, 0, 1, 0);
int sectorGroup = 0;
std::vector<LevelMeshSurface*> surfaces;
};
void LevelSubmesh::BuildTileSurfaceLists()
{
// Smoothing group surface is to be rendered with
TArray<LevelMeshSmoothingGroup> SmoothingGroups;
TArray<int> SmoothingGroupIndexes(GetSurfaceCount());
// Plane group surface is to be rendered with
TArray<LevelMeshPlaneGroup> PlaneGroups;
TArray<int> PlaneGroupIndexes(GetSurfaceCount());
for (int i = 0, count = GetSurfaceCount(); i < count; i++)
{
auto surface = GetSurface(i);
// Is this surface in the same plane as an existing smoothing group?
int smoothingGroupIndex = -1;
// Is this surface in the same plane as an existing plane group?
int planeGroupIndex = -1;
for (size_t j = 0; j < SmoothingGroups.Size(); j++)
for (size_t j = 0; j < PlaneGroups.Size(); j++)
{
if (surface->SectorGroup == SmoothingGroups[j].sectorGroup)
if (surface->SectorGroup == PlaneGroups[j].sectorGroup)
{
float direction = SmoothingGroups[j].plane.XYZ() | surface->Plane.XYZ();
float direction = PlaneGroups[j].plane.XYZ() | surface->Plane.XYZ();
if (direction >= 0.9999f && direction <= 1.001f)
{
auto point = (surface->Plane.XYZ() * surface->Plane.W);
auto planeDistance = (SmoothingGroups[j].plane.XYZ() | point) - SmoothingGroups[j].plane.W;
auto planeDistance = (PlaneGroups[j].plane.XYZ() | point) - PlaneGroups[j].plane.W;
float dist = std::abs(planeDistance);
if (dist <= 0.01f)
{
smoothingGroupIndex = (int)j;
planeGroupIndex = (int)j;
break;
}
}
}
}
// Surface is in a new plane. Create a smoothing group for it
if (smoothingGroupIndex == -1)
// Surface is in a new plane. Create a plane group for it
if (planeGroupIndex == -1)
{
smoothingGroupIndex = SmoothingGroups.Size();
planeGroupIndex = PlaneGroups.Size();
LevelMeshSmoothingGroup group;
LevelMeshPlaneGroup group;
group.plane = surface->Plane;
group.sectorGroup = surface->SectorGroup;
SmoothingGroups.Push(group);
PlaneGroups.Push(group);
}
SmoothingGroups[smoothingGroupIndex].surfaces.push_back(surface);
SmoothingGroupIndexes.Push(smoothingGroupIndex);
PlaneGroups[planeGroupIndex].surfaces.push_back(surface);
PlaneGroupIndexes.Push(planeGroupIndex);
}
for (auto& tile : LightmapTiles)
tile.Surfaces.Clear();
for (int i = 0, count = GetSurfaceCount(); i < count; i++)
{
auto targetSurface = GetSurface(i);
targetSurface->TileSurfaces.Clear();
for (LevelMeshSurface* surface : SmoothingGroups[SmoothingGroupIndexes[i]].surfaces)
LevelMeshSurface* targetSurface = GetSurface(i);
if (targetSurface->LightmapTileIndex < 0)
continue;
LightmapTile* tile = &LightmapTiles[targetSurface->LightmapTileIndex];
for (LevelMeshSurface* surface : PlaneGroups[PlaneGroupIndexes[i]].surfaces)
{
FVector2 minUV = ToUV(surface->Bounds.min, targetSurface);
FVector2 maxUV = ToUV(surface->Bounds.max, targetSurface);
FVector2 minUV = tile->ToUV(surface->Bounds.min);
FVector2 maxUV = tile->ToUV(surface->Bounds.max);
if (surface != targetSurface && (maxUV.X < 0.0f || maxUV.Y < 0.0f || minUV.X > 1.0f || minUV.Y > 1.0f))
continue; // Bounding box not visible
targetSurface->TileSurfaces.Push(surface);
tile->Surfaces.Push(surface);
}
}
}
FVector2 LevelSubmesh::ToUV(const FVector3& vert, const LevelMeshSurface* targetSurface)
{
FVector3 localPos = vert - targetSurface->TileTransform.TranslateWorldToLocal;
float u = (1.0f + (localPos | targetSurface->TileTransform.ProjLocalToU)) / (targetSurface->AtlasTile.Width + 2);
float v = (1.0f + (localPos | targetSurface->TileTransform.ProjLocalToV)) / (targetSurface->AtlasTile.Height + 2);
return FVector2(u, v);
}

View file

@ -7,12 +7,13 @@
#include "flatvertices.h"
#include "hw_levelmeshlight.h"
#include "hw_levelmeshportal.h"
#include "hw_lightmaptile.h"
#include "hw_levemeshsurface.h"
#include "hw_materialstate.h"
#include "hw_surfaceuniforms.h"
#include <memory>
struct LevelMeshSurfaceStats;
struct LevelMeshTileStats;
struct LevelSubmeshDrawRange
{
@ -48,19 +49,21 @@ public:
// Lightmap atlas
int LMTextureCount = 0;
int LMTextureSize = 0;
int LMTextureSize = 1024;
TArray<uint16_t> LMTextureData;
uint16_t LightmapSampleDistance = 16;
TArray<LightmapTile> LightmapTiles;
uint32_t AtlasPixelCount() const { return uint32_t(LMTextureCount * LMTextureSize * LMTextureSize); }
void UpdateCollision();
void GatherSurfacePixelStats(LevelMeshSurfaceStats& stats);
void GatherTilePixelStats(LevelMeshTileStats& stats);
void BuildTileSurfaceLists();
private:
FVector2 ToUV(const FVector3& vert, const LevelMeshSurface* targetSurface);
FVector2 ToUV(const FVector3& vert, const LightmapTile* tile);
};
class LevelMesh
@ -76,7 +79,7 @@ public:
LevelMeshSurface* Trace(const FVector3& start, FVector3 direction, float maxDist);
LevelMeshSurfaceStats GatherSurfacePixelStats();
LevelMeshTileStats GatherTilePixelStats();
// Map defaults
FVector3 SunDirection = FVector3(0.0f, 0.0f, -1.0f);
@ -85,12 +88,12 @@ public:
TArray<LevelMeshPortal> Portals;
};
struct LevelMeshSurfaceStats
struct LevelMeshTileStats
{
struct Stats
{
uint32_t total = 0, dirty = 0, sky = 0;
uint32_t total = 0, dirty = 0;
};
Stats surfaces, pixels;
Stats tiles, pixels;
};

View file

@ -9,6 +9,7 @@
class LevelSubmesh;
class FGameTexture;
struct LevelMeshSurface;
struct LevelMeshSurface
{
@ -22,21 +23,10 @@ struct LevelMeshSurface
unsigned int NumElements = 0;
} MeshLocation;
BBox Bounds;
FVector4 Plane = FVector4(0.0f, 0.0f, 1.0f, 0.0f);
int LightmapTileIndex = -1;
// Surface location in lightmap texture
struct
{
int X = 0;
int Y = 0;
int Width = 0;
int Height = 0;
int ArrayIndex = 0;
uint32_t Area() const { return Width * Height; }
} AtlasTile;
// True if the surface needs to be rendered into the lightmap texture before it can be used
bool NeedsUpdate = true;
bool AlwaysUpdate = false;
FGameTexture* Texture = nullptr;
@ -46,20 +36,6 @@ struct LevelMeshSurface
int PortalIndex = 0;
int SectorGroup = 0;
BBox Bounds;
uint16_t SampleDimension = 0;
// Calculate world coordinates to UV coordinates
struct
{
FVector3 TranslateWorldToLocal = { 0.0f, 0.0f, 0.0f };
FVector3 ProjLocalToU = { 0.0f, 0.0f, 0.0f };
FVector3 ProjLocalToV = { 0.0f, 0.0f, 0.0f };
} TileTransform;
// Surfaces that are visible within the lightmap tile
TArray<LevelMeshSurface*> TileSurfaces;
// Light list location in the lightmapper GPU buffers
struct
{
@ -68,10 +44,3 @@ struct LevelMeshSurface
int ResetCounter = -1;
} LightList;
};
struct LevelMeshSmoothingGroup
{
FVector4 plane = FVector4(0, 0, 1, 0);
int sectorGroup = 0;
std::vector<LevelMeshSurface*> surfaces;
};

View file

@ -0,0 +1,64 @@
#pragma once
#include "tarray.h"
#include "vectors.h"
#include "bounds.h"
struct LevelMeshSurface;
struct LightmapTileBinding
{
uint32_t Type = 0;
uint32_t TypeIndex = 0;
uint32_t ControlSector = 0xffffffff;
bool operator<(const LightmapTileBinding& other) const
{
if (TypeIndex != other.TypeIndex) return TypeIndex < other.TypeIndex;
if (ControlSector != other.ControlSector) return ControlSector < other.ControlSector;
return Type < other.Type;
}
};
struct LightmapTile
{
// Surface location in lightmap texture
struct
{
int X = 0;
int Y = 0;
int Width = 0;
int Height = 0;
int ArrayIndex = 0;
uint32_t Area() const { return Width * Height; }
} AtlasLocation;
// Calculate world coordinates to UV coordinates
struct
{
FVector3 TranslateWorldToLocal = { 0.0f, 0.0f, 0.0f };
FVector3 ProjLocalToU = { 0.0f, 0.0f, 0.0f };
FVector3 ProjLocalToV = { 0.0f, 0.0f, 0.0f };
} Transform;
LightmapTileBinding Binding;
// Surfaces that are visible within the lightmap tile
TArray<LevelMeshSurface*> Surfaces;
BBox Bounds;
uint16_t SampleDimension = 0;
FVector4 Plane = FVector4(0.0f, 0.0f, 1.0f, 0.0f);
// True if the tile needs to be rendered into the lightmap texture before it can be used
bool NeedsUpdate = true;
FVector2 ToUV(const FVector3& vert) const
{
FVector3 localPos = vert - Transform.TranslateWorldToLocal;
float u = (1.0f + (localPos | Transform.ProjLocalToU)) / (AtlasLocation.Width + 2);
float v = (1.0f + (localPos | Transform.ProjLocalToV)) / (AtlasLocation.Height + 2);
return FVector2(u, v);
}
};

View file

@ -139,7 +139,7 @@ public:
virtual bool IsPoly() { return false; }
virtual bool CompileNextShader() { return true; }
virtual void SetLevelMesh(LevelMesh *mesh) { }
virtual void UpdateLightmaps(const TArray<LevelMeshSurface*>& surfaces) {}
virtual void UpdateLightmaps(const TArray<LightmapTile*>& tiles) {}
virtual DCanvas* GetCanvas() { return nullptr; }

View file

@ -640,7 +640,6 @@ void VkLevelMeshUploader::UploadSurfaces()
SurfaceInfo info;
info.Normal = FVector3(surface->Plane.X, surface->Plane.Z, surface->Plane.Y);
info.PortalIndex = surface->PortalIndex;
info.SamplingDistance = (float)surface->SampleDimension;
info.Sky = surface->IsSky;
info.Alpha = surface->Alpha;
if (surface->Texture)

View file

@ -31,10 +31,10 @@ struct SurfaceInfo
{
FVector3 Normal;
float Sky;
float SamplingDistance;
uint32_t PortalIndex;
int32_t TextureIndex;
float Alpha;
float Padding;
};
struct PortalInfo

View file

@ -92,16 +92,16 @@ void VkLightmapper::BeginFrame()
drawindexed.Pos = 0;
}
void VkLightmapper::Raytrace(const TArray<LevelMeshSurface*>& surfaces)
void VkLightmapper::Raytrace(const TArray<LightmapTile*>& tiles)
{
if (mesh && surfaces.Size() > 0)
if (mesh && tiles.Size() > 0)
{
lightmapRaytraceLast.active = true;
lightmapRaytraceLast.ResetAndClock();
SelectSurfaces(surfaces);
if (selectedSurfaces.Size() > 0)
SelectTiles(tiles);
if (selectedTiles.Size() > 0)
{
fb->GetCommands()->PushGroup(fb->GetCommands()->GetTransferCommands(), "lightmap.total");
@ -119,36 +119,36 @@ void VkLightmapper::Raytrace(const TArray<LevelMeshSurface*>& surfaces)
}
}
void VkLightmapper::SelectSurfaces(const TArray<LevelMeshSurface*>& surfaces)
void VkLightmapper::SelectTiles(const TArray<LightmapTile*>& tiles)
{
bakeImage.maxX = 0;
bakeImage.maxY = 0;
selectedSurfaces.Clear();
selectedTiles.Clear();
const int spacing = 5; // Note: the spacing is here to avoid that the resolve sampler finds data from other surface tiles
RectPacker packer(bakeImageSize - spacing, bakeImageSize - spacing, RectPacker::Spacing(spacing));
for (int i = 0, count = surfaces.Size(); i < count; i++)
for (int i = 0, count = tiles.Size(); i < count; i++)
{
LevelMeshSurface* surface = surfaces[i];
LightmapTile* tile = tiles[i];
if (!surface->NeedsUpdate)
if (!tile->NeedsUpdate)
continue;
// Only grab surfaces until our bake texture is full
auto result = packer.insert(surface->AtlasTile.Width + 2, surface->AtlasTile.Height + 2);
auto result = packer.insert(tile->AtlasLocation.Width + 2, tile->AtlasLocation.Height + 2);
if (result.pageIndex == 0)
{
SelectedSurface selected;
selected.Surface = surface;
SelectedTile selected;
selected.Tile = tile;
selected.X = result.pos.x + 1;
selected.Y = result.pos.y + 1;
selectedSurfaces.Push(selected);
selectedTiles.Push(selected);
bakeImage.maxX = std::max<uint16_t>(bakeImage.maxX, uint16_t(selected.X + surface->AtlasTile.Width + spacing));
bakeImage.maxY = std::max<uint16_t>(bakeImage.maxY, uint16_t(selected.Y + surface->AtlasTile.Height + spacing));
bakeImage.maxX = std::max<uint16_t>(bakeImage.maxX, uint16_t(selected.X + tile->AtlasLocation.Width + spacing));
bakeImage.maxY = std::max<uint16_t>(bakeImage.maxY, uint16_t(selected.Y + tile->AtlasLocation.Height + spacing));
surface->NeedsUpdate = false;
tile->NeedsUpdate = false;
}
}
}
@ -180,35 +180,40 @@ void VkLightmapper::Render()
viewport.height = (float)bakeImageSize;
cmdbuffer->setViewport(0, 1, &viewport);
for (int i = 0, count = selectedSurfaces.Size(); i < count; i++)
{
auto& selectedSurface = selectedSurfaces[i];
LevelMeshSurface* targetSurface = selectedSurface.Surface;
int dynamicSurfaceIndexOffset = mesh->StaticMesh->GetSurfaceCount();
int dynamicFirstIndexOffset = mesh->StaticMesh->Mesh.Indexes.Size();
LevelSubmesh* staticMesh = mesh->StaticMesh.get();
int surfaceIndexOffset = 0;
int firstIndexOffset = 0;
if (targetSurface->Submesh != mesh->StaticMesh.get())
{
surfaceIndexOffset = mesh->StaticMesh->GetSurfaceCount();
firstIndexOffset = mesh->StaticMesh->Mesh.Indexes.Size();
}
for (int i = 0, count = selectedTiles.Size(); i < count; i++)
{
auto& selectedTile = selectedTiles[i];
LightmapTile* targetTile = selectedTile.Tile;
LightmapRaytracePC pc;
pc.TileX = (float)selectedSurface.X;
pc.TileY = (float)selectedSurface.Y;
pc.SurfaceIndex = surfaceIndexOffset + targetSurface->Submesh->GetSurfaceIndex(targetSurface);
pc.TileX = (float)selectedTile.X;
pc.TileY = (float)selectedTile.Y;
pc.TextureSize = (float)bakeImageSize;
pc.TileWidth = (float)targetSurface->AtlasTile.Width;
pc.TileHeight = (float)targetSurface->AtlasTile.Height;
pc.WorldToLocal = SwapYZ(targetSurface->TileTransform.TranslateWorldToLocal);
pc.ProjLocalToU = SwapYZ(targetSurface->TileTransform.ProjLocalToU);
pc.ProjLocalToV = SwapYZ(targetSurface->TileTransform.ProjLocalToV);
pc.TileWidth = (float)targetTile->AtlasLocation.Width;
pc.TileHeight = (float)targetTile->AtlasLocation.Height;
pc.WorldToLocal = SwapYZ(targetTile->Transform.TranslateWorldToLocal);
pc.ProjLocalToU = SwapYZ(targetTile->Transform.ProjLocalToU);
pc.ProjLocalToV = SwapYZ(targetTile->Transform.ProjLocalToV);
bool buffersFull = false;
// Paint all surfaces visible in the tile
for (LevelMeshSurface* surface : targetSurface->TileSurfaces)
for (LevelMeshSurface* surface : targetTile->Surfaces)
{
int surfaceIndexOffset = 0;
int firstIndexOffset = 0;
if (surface->Submesh != staticMesh)
{
surfaceIndexOffset = dynamicSurfaceIndexOffset;
firstIndexOffset = dynamicFirstIndexOffset;
}
pc.SurfaceIndex = surfaceIndexOffset + surface->Submesh->GetSurfaceIndex(surface);
if (surface->LightList.ResetCounter != lights.ResetCounter)
{
int lightCount = mesh->AddSurfaceLights(surface, templightlist.Data(), (int)templightlist.Size());
@ -272,13 +277,13 @@ void VkLightmapper::Render()
{
while (i < count)
{
selectedSurfaces[i].Surface->NeedsUpdate = true;
selectedTiles[i].Tile->NeedsUpdate = true;
i++;
}
break;
}
selectedSurface.Rendered = true;
selectedTile.Rendered = true;
}
#ifdef USE_DRAWINDIRECT
@ -407,19 +412,19 @@ void VkLightmapper::CopyResult()
uint32_t pixels = 0;
lastSurfaceCount = 0;
for (auto& list : copylists) list.Clear();
for (int i = 0, count = selectedSurfaces.Size(); i < count; i++)
for (int i = 0, count = selectedTiles.Size(); i < count; i++)
{
auto& selected = selectedSurfaces[i];
auto& selected = selectedTiles[i];
if (selected.Rendered)
{
unsigned int pageIndex = (unsigned int)selected.Surface->AtlasTile.ArrayIndex;
unsigned int pageIndex = (unsigned int)selected.Tile->AtlasLocation.ArrayIndex;
if (pageIndex >= copylists.Size())
{
copylists.Resize(pageIndex + 1);
}
copylists[pageIndex].Push(&selected);
pixels += selected.Surface->AtlasTile.Area();
pixels += selected.Tile->AtlasLocation.Area();
lastSurfaceCount++;
}
}
@ -484,17 +489,17 @@ void VkLightmapper::CopyResult()
// Copy the tile positions into a storage buffer for the vertex shader to read
start = pos;
for (SelectedSurface* selected : list)
for (SelectedTile* selected : list)
{
LevelMeshSurface* surface = selected->Surface;
LightmapTile* tile = selected->Tile;
CopyTileInfo* copyinfo = &copytiles.Tiles[pos++];
copyinfo->SrcPosX = selected->X;
copyinfo->SrcPosY = selected->Y;
copyinfo->DestPosX = surface->AtlasTile.X;
copyinfo->DestPosY = surface->AtlasTile.Y;
copyinfo->TileWidth = surface->AtlasTile.Width;
copyinfo->TileHeight = surface->AtlasTile.Height;
copyinfo->DestPosX = tile->AtlasLocation.X;
copyinfo->DestPosY = tile->AtlasLocation.Y;
copyinfo->TileWidth = tile->AtlasLocation.Width;
copyinfo->TileHeight = tile->AtlasLocation.Height;
}
// Draw the tiles. One instance per tile.

View file

@ -95,9 +95,9 @@ struct LightInfo
float Padding3;
};
struct SelectedSurface
struct SelectedTile
{
LevelMeshSurface* Surface = nullptr;
LightmapTile* Tile = nullptr;
int X = -1;
int Y = -1;
bool Rendered = false;
@ -126,13 +126,13 @@ public:
~VkLightmapper();
void BeginFrame();
void Raytrace(const TArray<LevelMeshSurface*>& surfaces);
void Raytrace(const TArray<LightmapTile*>& surfaces);
void SetLevelMesh(LevelMesh* level);
private:
void ReleaseResources();
void SelectSurfaces(const TArray<LevelMeshSurface*>& surfaces);
void SelectTiles(const TArray<LightmapTile*>& surfaces);
void UploadUniforms();
void Render();
void Resolve();
@ -165,8 +165,8 @@ private:
bool useRayQuery = true;
TArray<SelectedSurface> selectedSurfaces;
TArray<TArray<SelectedSurface*>> copylists;
TArray<SelectedTile> selectedTiles;
TArray<TArray<SelectedTile*>> copylists;
TArray<LevelMeshLight> templightlist;
struct

View file

@ -553,9 +553,9 @@ void VulkanRenderDevice::SetLevelMesh(LevelMesh* mesh)
levelMeshChanged = true;
}
void VulkanRenderDevice::UpdateLightmaps(const TArray<LevelMeshSurface*>& surfaces)
void VulkanRenderDevice::UpdateLightmaps(const TArray<LightmapTile*>& tiles)
{
GetLightmapper()->Raytrace(surfaces);
GetLightmapper()->Raytrace(tiles);
}
void VulkanRenderDevice::SetShadowMaps(const TArray<float>& lights, hwrenderer::LevelAABBTree* tree, bool newTree)

View file

@ -65,7 +65,7 @@ public:
void AmbientOccludeScene(float m5) override;
void SetSceneRenderTarget(bool useSSAO) override;
void SetLevelMesh(LevelMesh* mesh) override;
void UpdateLightmaps(const TArray<LevelMeshSurface*>& surfaces) override;
void UpdateLightmaps(const TArray<LightmapTile*>& tiles) override;
void SetShadowMaps(const TArray<float>& lights, hwrenderer::LevelAABBTree* tree, bool newTree) override;
void SetSaveBuffers(bool yes) override;
void ImageTransitionScene(bool unknown) override;