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;

View file

@ -3012,18 +3012,12 @@ void MapLoader::InitLevelMesh(MapData* map)
Level->levelMesh = new DoomLevelMesh(*Level);
// Lightmap binding/loading
if (Level->lightmaps)
{
if (!LoadLightmap(map))
{
Level->levelMesh->PackLightmapAtlas();
}
}
LoadLightmap(map);
}
bool MapLoader::LoadLightmap(MapData* map)
{
if (!map->Size(ML_LIGHTMAP))
if (!Level->lightmaps || !map->Size(ML_LIGHTMAP))
return false;
FileReader fr;
@ -3031,65 +3025,34 @@ bool MapLoader::LoadLightmap(MapData* map)
return false;
int version = fr.ReadInt32();
if (version == 0)
{
Printf(PRINT_HIGH, "LoadLightmap: This is an old unsupported alpha version of the lightmap lump. Please rebuild the map with a newer version of zdray.\n");
return false;
}
if (version == 1)
if (version < 2)
{
Printf(PRINT_HIGH, "LoadLightmap: This is an old unsupported version of the lightmap lump. Please rebuild the map with a newer version of zdray.\n");
return false;
}
if (version != 2)
else if (version != 2)
{
Printf(PRINT_HIGH, "LoadLightmap: unsupported lightmap lump version\n");
return false;
}
uint32_t numSurfaces = fr.ReadUInt32();
uint32_t numTiles = fr.ReadUInt32();
uint32_t numTexPixels = fr.ReadUInt32();
uint32_t numTexCoords = fr.ReadUInt32();
uint32_t numTexCoords = fr.ReadUInt32(); // To do: remove from a future version of the format. We don't need this.
if (developer >= 5)
{
Printf("LoadLightmap: Surfaces: %u, Pixels: %u, UVs: %u\n", numSurfaces, numTexPixels, numTexCoords);
Printf("LoadLightmap: Tiles: %u, Pixels: %u, UVs: %u\n", numTiles, numTexPixels, numTexCoords);
}
if (numSurfaces == 0 || numTexCoords == 0 || numTexPixels == 0)
if (numTiles == 0 || numTexCoords == 0 || numTexPixels == 0)
return false;
bool errors = false;
int errors = 0;
// Load the surfaces we have lightmap data for
// Load the tiles we have lightmap data for
const int surfaceTypes = 5; // ST_CEILING, ST_FLOOR, etc.
TMap<int, TArray<DoomLevelMeshSurface*>> surfaceGroups[surfaceTypes]; // Let's assume there is only a handful of 3d floor surfaces matching the same typeIndex
auto findSurfaceIndex = [&](int type, int index, const sector_t* sec) {
const auto* surfaces = surfaceGroups[type - 1].CheckKey(index);
if (surfaces)
{
for (unsigned i = 0, count = surfaces->Size(); i < count; ++i)
{
const auto surface = (*surfaces)[i];
if (surface->ControlSector == sec)
{
return Level->levelMesh->StaticMesh->GetSurfaceIndex(surface);
}
}
}
return 0xffffffff;
};
auto getControlSector = [&](uint32_t index)
{
return index < Level->sectors.Size() ? &Level->sectors[index] : nullptr;
};
struct SurfaceEntry // V2 entries
struct TileEntry // V2 entries
{
uint32_t type, typeIndex;
uint32_t controlSector; // 0xFFFFFFFF is none
@ -3100,114 +3063,21 @@ bool MapLoader::LoadLightmap(MapData* map)
DoomLevelMeshSurface* targetSurface;
};
TMap<DoomLevelMeshSurface*, int> detectedSurfaces;
TArray<SurfaceEntry> zdraySurfaces;
zdraySurfaces.Reserve(numSurfaces);
auto submesh = static_cast<DoomLevelSubmesh*>(Level->levelMesh->StaticMesh.get());
for (auto& surface : submesh->Surfaces)
{
surface.NeedsUpdate = false; // let's consider everything valid until we make a mistake trying to change this surface
if (surface.Type > ST_NONE && surface.Type <= ST_FLOOR)
{
if (auto list = surfaceGroups[surface.Type - 1].CheckKey(surface.TypeIndex))
{
list->Push(&surface);
}
else
{
surfaceGroups[surface.Type - 1].InsertNew(surface.TypeIndex).Push(&surface);
}
}
}
TArray<TileEntry> tileEntries;
tileEntries.Reserve(numTiles);
uint32_t usedSurfaceIndex = 0;
for (uint32_t i = 0; i < numSurfaces; i++)
for (uint32_t i = 0; i < numTiles; i++)
{
SurfaceEntry surface;
surface.type = fr.ReadUInt32();
surface.typeIndex = fr.ReadUInt32();
surface.controlSector = fr.ReadUInt32();
surface.width = fr.ReadUInt16();
surface.height = fr.ReadUInt16();
surface.pixelsOffset = fr.ReadUInt32();
surface.uvCount = fr.ReadUInt32();
surface.uvOffset = fr.ReadUInt32();
auto controlSector = getControlSector(surface.controlSector);
// Check against the internal levelmesh
if (surface.type <= ST_NONE || surface.type > ST_FLOOR)
{
errors = true;
if (developer >= 1)
{
Printf(PRINT_HIGH, "Lightmap lump surface index %d uses invalid type %d\n", i, surface.type);
}
continue;
}
if (i >= submesh->Surfaces.Size())
{
errors = true;
if (developer >= 1)
{
Printf(PRINT_HIGH, "Lightmap lump surface index %d is out of bounds\n", i);
}
continue;
}
auto levelSurface = &submesh->Surfaces[i];
if (levelSurface->Type != surface.type || levelSurface->TypeIndex != surface.typeIndex || levelSurface->ControlSector != controlSector)
{
auto internalIndex = findSurfaceIndex(surface.type, surface.typeIndex, controlSector);
if (internalIndex < submesh->Surfaces.Size())
{
levelSurface = &submesh->Surfaces[internalIndex];
}
else
{
errors = true;
if (developer >= 1)
{
Printf(PRINT_HIGH, "Lightmap lump surface %d mismatch. Couldn't find surface type:%d, typeindex:%d, controlsector:%d\n", i, surface.type, surface.typeIndex, surface.controlSector);
}
// TODO detailed printout
continue;
}
}
if (auto ptr = detectedSurfaces.CheckKey(levelSurface))
{
(*ptr)++;
if (developer >= 1)
{
Printf(PRINT_HIGH, "Lightmap lump surface index %d is referencing surface %d (ref count: %d). Surface type:%d, typeindex:%d, controlsector:%d\n", i, submesh->GetSurfaceIndex(levelSurface), *ptr, surface.type, surface.typeIndex, surface.controlSector);
}
}
else
{
levelSurface->AtlasTile.Width = surface.width;
levelSurface->AtlasTile.Height = surface.height;
surface.targetSurface = levelSurface;
detectedSurfaces.Insert(levelSurface, 1);
zdraySurfaces[usedSurfaceIndex++] = surface;
}
}
Level->levelMesh->PackLightmapAtlas();
zdraySurfaces.Resize(usedSurfaceIndex);
if (developer >= 1)
{
Printf("Lightmap contains %d surfaces out of which %d were successfully matched.\n", numSurfaces, zdraySurfaces.Size());
TileEntry& entry = tileEntries[i];
entry.type = fr.ReadUInt32();
entry.typeIndex = fr.ReadUInt32();
entry.controlSector = fr.ReadUInt32();
entry.width = fr.ReadUInt16();
entry.height = fr.ReadUInt16();
entry.pixelsOffset = fr.ReadUInt32();
entry.uvCount = fr.ReadUInt32();
entry.uvOffset = fr.ReadUInt32();
}
// Load pixels
@ -3216,184 +3086,81 @@ bool MapLoader::LoadLightmap(MapData* map)
uint8_t* data = (uint8_t*)&textureData[0];
fr.Read(data, numTexPixels * 3 * sizeof(uint16_t));
// Load texture coordinates
TArray<FVector2> zdrayUvs;
zdrayUvs.Resize(numTexCoords);
fr.Read(&zdrayUvs[0], numTexCoords * 2 * sizeof(float));
// Load texture coordinates
// TArray<FVector2> zdrayUvs;
// zdrayUvs.Resize(numTexCoords);
// fr.Read(&zdrayUvs[0], numTexCoords * 2 * sizeof(float));
// Load lightmap textures
auto submesh = Level->levelMesh->StaticMesh.get();
const auto textureSize = submesh->LMTextureSize;
// Start with empty lightmap textures
submesh->LMTextureData.Resize(submesh->LMTextureCount * textureSize * textureSize * 3);
memset(submesh->LMTextureData.Data(), 0, submesh->LMTextureData.Size() * sizeof(uint16_t));
auto pixels = &submesh->LMTextureData[0];
for (int i = 0, count = submesh->LMTextureData.Size(); i < count; i += 3)
// Create lookup for finding tiles
std::map<LightmapTileBinding, LightmapTile*> levelTiles;
for (LightmapTile& tile : submesh->LightmapTiles)
{
pixels[i] = floatToHalf(0.0);
pixels[i + 1] = floatToHalf(0.0);
pixels[i + 2] = floatToHalf(0.0);
levelTiles[tile.Binding] = &tile;
}
#if 0 // debug surface mapping
for (auto& surface : submesh->Surfaces)
// Bind tiles and copy their pixels to the texture
for (const TileEntry& entry : tileEntries)
{
int dstX = surface.AtlasTile.X;
int dstY = surface.AtlasTile.Y;
int dstPage = surface.AtlasTile.ArrayIndex;
LightmapTileBinding binding;
binding.Type = entry.type;
binding.TypeIndex = entry.typeIndex;
binding.ControlSector = entry.controlSector;
// copy pixels
uint16_t* dst = &submesh->LMTextureData[dstPage * textureSize * textureSize * 3];
uint32_t srcIndex = 0;
if (auto ptr = detectedSurfaces.CheckKey(&surface))
auto it = levelTiles.find(binding);
if (it == levelTiles.end())
{
for (int y = 0; y < surface.AtlasTile.Height; ++y)
{
for (int x = 0; x < surface.AtlasTile.Width; ++x)
{
uint32_t dstIndex = uint32_t(dstX + x + (dstY + y) * textureSize) * 3;
if (errors < 10 && developer >= 1)
Printf("Could not find lightmap tile in level mesh (type = %d, index = %d, control sector = %d)\n", entry.type, entry.typeIndex, entry.controlSector);
errors++;
continue;
}
dst[dstIndex] = floatToHalf(1.0);
dst[dstIndex + 1] = floatToHalf(0.0);
dst[dstIndex + 2] = floatToHalf(0.0);
}
LightmapTile* tile = it->second;
// To do: add transform info to the lump so that we can stretch the pixels as the lump lightmapper might be using fixed point coordinates that could cause alignment issues
if (tile->AtlasLocation.Width != entry.width || tile->AtlasLocation.Height != entry.height)
{
if (errors < 10 && developer >= 1)
Printf("Lightmap tile size mismatch (type = %d, index = %d, control sector = %d)\n", entry.type, entry.typeIndex, entry.controlSector);
errors++;
continue;
}
const uint16_t* src = textureData.Data() + entry.pixelsOffset;
uint16_t* dst = &submesh->LMTextureData[tile->AtlasLocation.ArrayIndex * textureSize * textureSize * 3];
int x = tile->AtlasLocation.X;
int y = tile->AtlasLocation.Y;
int w = tile->AtlasLocation.Width;
int h = tile->AtlasLocation.Height;
for (int yy = 0; yy < w; yy++)
{
const uint16_t* srcline = src + yy * w * 3;
uint16_t* dstline = dst + (x + yy * textureSize) * 3;
for (int xx = 0, end = w * 3; xx < end; xx++)
{
dstline[xx] = srcline[xx];
}
}
tile->NeedsUpdate = false;
}
if (errors > 0)
{
if (developer <= 0)
Printf(PRINT_HIGH, "Pre-calculated LIGHTMAP surfaces do not match current level surfaces. Restart this level with 'developer 1' for further details.\nPerhaps you forget to rebuild lightmaps after modifying the map?\n");
else
{
for (int y = 0; y < surface.AtlasTile.Height; ++y)
{
for (int x = 0; x < surface.AtlasTile.Width; ++x)
{
uint32_t dstIndex = uint32_t(dstX + x + (dstY + y) * textureSize) * 3;
dst[dstIndex] = floatToHalf(0.0);
dst[dstIndex + 1] = floatToHalf(0.0);
dst[dstIndex + 2] = floatToHalf(0.0);
}
}
}
}
#endif
// Map surface pixels into atlas
int index = -1;
for (auto& surface : zdraySurfaces)
{
++index; // for debug output
auto& realSurface = *surface.targetSurface;
// calculate pixel positions
const uint32_t srcPixelOffset = surface.pixelsOffset;
const int dstX = realSurface.AtlasTile.X;
const int dstY = realSurface.AtlasTile.Y;
const int dstPage = realSurface.AtlasTile.ArrayIndex;
// Sanity checks
if (dstX < 0 || dstY < 0 || dstX + surface.width > textureSize || dstY + surface.height > textureSize || dstPage >= submesh->LMTextureCount)
{
errors = true;
if (developer >= 1)
{
Printf("Can't map lightmap surface %d pixels[%u] -> ((x:%d, y:%d), (x2:%d, y2:%d), page:%d)\n", index, srcPixelOffset, dstX, dstY, dstX + surface.width, dstY + surface.height, dstPage);
}
realSurface.NeedsUpdate = true;
continue;
}
if (realSurface.AtlasTile.Width != surface.width || realSurface.AtlasTile.Height != surface.height)
{
errors = true;
if (developer >= 1)
{
Printf("Surface size mismatch: Attempting to remap %dx%d to %dx%d pixel area.\n", surface.width, surface.height, realSurface.AtlasTile.Width, realSurface.AtlasTile.Height);
}
realSurface.NeedsUpdate = true;
continue;
}
if (developer >= 5)
{
Printf("Mapping lightmap surface pixels[%u] (count: %u) -> ((x:%d, y:%d), (x2:%d, y2:%d), page:%d) area: %u\n",
srcPixelOffset, surface.width * surface.height * 3,
dstX, dstY,
dstX + realSurface.AtlasTile.Width, dstY + realSurface.AtlasTile.Height,
dstPage,
realSurface.AtlasTile.Area() * 3);
}
// copy pixels
uint32_t srcIndex = 0;
uint16_t* src = &textureData[srcPixelOffset];
uint16_t* dst = &submesh->LMTextureData[realSurface.AtlasTile.ArrayIndex * textureSize * textureSize * 3];
int endY = realSurface.AtlasTile.Y + realSurface.AtlasTile.Height;
int endX = realSurface.AtlasTile.X + realSurface.AtlasTile.Width;
for (int y = realSurface.AtlasTile.Y; y < endY; ++y)
{
for (int x = realSurface.AtlasTile.X; x < endX; ++x)
{
uint32_t dstIndex = uint32_t(x + (y * textureSize)) * 3;
dst[dstIndex] = src[srcIndex++];
dst[dstIndex + 1] = src[srcIndex++];
dst[dstIndex + 2] = src[srcIndex++];
}
}
}
// Use UVs from the lightmap
for (auto& surface : zdraySurfaces)
{
auto& realSurface = *surface.targetSurface;
auto* vertices = &submesh->Mesh.Vertices[realSurface.MeshLocation.StartVertIndex];
auto* newUVs = &zdrayUvs[surface.uvOffset];
for (uint32_t i = 0; i < surface.uvCount; ++i)
{
FVector2 oldUv = FVector2(vertices[i].lu, vertices[i].lv);
if (developer >= 5)
{
Printf("Old UV: %.6f %.6f (w:%d, h:%d) (x:%d, y:%d), Lump UVs %.3f %.3f\n", vertices[i].lu, vertices[i].lv, realSurface.AtlasTile.Width, realSurface.AtlasTile.Height, realSurface.AtlasTile.X, realSurface.AtlasTile.Y, newUVs[i].X, newUVs[i].Y);
}
// Finish surface
vertices[i].lu = (newUVs[i].X + realSurface.AtlasTile.X) / textureSize;
vertices[i].lv = (newUVs[i].Y + realSurface.AtlasTile.Y) / textureSize;
if (developer >= 5)
{
if (abs(oldUv.X - vertices[i].lu) >= 0.0000001f || abs(oldUv.Y - vertices[i].lv) >= 0.0000001f)
{
Printf("New UV: %.6f %.6f\n", vertices[i].lu, vertices[i].lv);
}
}
}
}
if (developer >= 3)
{
int loadedSurfaces = 0;
for (auto& surface : submesh->Surfaces)
{
if (!surface.NeedsUpdate)
{
loadedSurfaces++;
}
}
Printf(PRINT_HIGH, "%d/%d surfaces were successfully loaded from lightmap.\n", loadedSurfaces, submesh->Surfaces.Size());
}
if (errors && developer <= 0)
{
Printf(PRINT_HIGH, "Pre-calculated LIGHTMAP surfaces do not match current level surfaces. Restart this level with 'developer 1' for further details.\nPerhaps you forget to rebuild lightmaps after modifying the map?\n");
Printf(PRINT_HIGH, "Pre-calculated LIGHTMAP surfaces do not match current level surfaces.\nPerhaps you forget to rebuild lightmaps after modifying the map?\n");
}
return true;

View file

@ -43,10 +43,10 @@ ADD_STAT(lightmap)
}
uint32_t atlasPixelCount = levelMesh->StaticMesh->AtlasPixelCount();
auto stats = levelMesh->GatherSurfacePixelStats();
auto stats = levelMesh->GatherTilePixelStats();
out.Format("Surfaces: %u (sky: %u, awaiting updates: %u)\nSurface pixel area to update: %u\nSurface pixel area: %u\nAtlas pixel area: %u\nAtlas efficiency: %.4f%%",
stats.surfaces.total, stats.surfaces.sky, std::max(stats.surfaces.dirty - stats.surfaces.sky, (uint32_t)0),
out.Format("Surfaces: %u (awaiting updates: %u)\nSurface pixel area to update: %u\nSurface pixel area: %u\nAtlas pixel area: %u\nAtlas efficiency: %.4f%%",
stats.tiles.total, stats.tiles.dirty,
stats.pixels.dirty,
stats.pixels.total,
atlasPixelCount,
@ -67,13 +67,13 @@ CCMD(invalidatelightmap)
if (!RequireLightmap()) return;
int count = 0;
for (auto& surface : static_cast<DoomLevelSubmesh*>(level.levelMesh->StaticMesh.get())->Surfaces)
for (auto& tile : level.levelMesh->StaticMesh->LightmapTiles)
{
if (!surface.NeedsUpdate)
if (!tile.NeedsUpdate)
++count;
surface.NeedsUpdate = true;
tile.NeedsUpdate = true;
}
Printf("Marked %d out of %d surfaces for update.\n", count, level.levelMesh->StaticMesh->GetSurfaceCount());
Printf("Marked %d out of %d tiles for update.\n", count, level.levelMesh->StaticMesh->LightmapTiles.Size());
}
void PrintSurfaceInfo(const DoomLevelMeshSurface* surface)
@ -83,10 +83,14 @@ void PrintSurfaceInfo(const DoomLevelMeshSurface* surface)
auto gameTexture = surface->Texture;
Printf("Surface %d (%p)\n Type: %d, TypeIndex: %d, ControlSector: %d\n", surface->Submesh->GetSurfaceIndex(surface), surface, surface->Type, surface->TypeIndex, surface->ControlSector ? surface->ControlSector->Index() : -1);
Printf(" Atlas page: %d, x:%d, y:%d\n", surface->AtlasTile.ArrayIndex, surface->AtlasTile.X, surface->AtlasTile.Y);
Printf(" Pixels: %dx%d (area: %d)\n", surface->AtlasTile.Width, surface->AtlasTile.Height, surface->AtlasTile.Area());
Printf(" Sample dimension: %d\n", surface->SampleDimension);
Printf(" Needs update?: %d\n", surface->NeedsUpdate);
if (surface->LightmapTileIndex >= 0)
{
LightmapTile* tile = &surface->Submesh->LightmapTiles[surface->LightmapTileIndex];
Printf(" Atlas page: %d, x:%d, y:%d\n", tile->AtlasLocation.ArrayIndex, tile->AtlasLocation.X, tile->AtlasLocation.Y);
Printf(" Pixels: %dx%d (area: %d)\n", tile->AtlasLocation.Width, tile->AtlasLocation.Height, tile->AtlasLocation.Area());
Printf(" Sample dimension: %d\n", tile->SampleDimension);
Printf(" Needs update?: %d\n", tile->NeedsUpdate);
}
Printf(" Always update?: %d\n", surface->AlwaysUpdate);
Printf(" Sector group: %d\n", surface->SectorGroup);
Printf(" Texture: '%s'\n", gameTexture ? gameTexture->GetName().GetChars() : "<nullptr>");
@ -160,11 +164,6 @@ bool DoomLevelMesh::TraceSky(const FVector3& start, FVector3 direction, float di
return surface && surface->IsSky;
}
void DoomLevelMesh::PackLightmapAtlas()
{
static_cast<DoomLevelSubmesh*>(StaticMesh.get())->PackLightmapAtlas(0);
}
int DoomLevelMesh::AddSurfaceLights(const LevelMeshSurface* surface, LevelMeshLight* list, int listMaxSize)
{
FLightNode* node = GetSurfaceLightNode(static_cast<const DoomLevelMeshSurface*>(surface));
@ -317,11 +316,16 @@ void DoomLevelMesh::DumpMesh(const FString& objFilename, const FString& mtlFilen
{
const auto& surface = submesh->Surfaces[index];
fprintf(f, "o Surface[%d] %s %d%s\n", index, name(surface.Type), surface.TypeIndex, surface.IsSky ? " sky" : "");
fprintf(f, "usemtl lightmap%d\n", surface.AtlasTile.ArrayIndex);
if (surface.AtlasTile.ArrayIndex > highestUsedAtlasPage)
if (surface.LightmapTileIndex >= 0)
{
highestUsedAtlasPage = surface.AtlasTile.ArrayIndex;
auto& tile = submesh->LightmapTiles[surface.LightmapTileIndex];
fprintf(f, "usemtl lightmap%d\n", tile.AtlasLocation.ArrayIndex);
if (tile.AtlasLocation.ArrayIndex > highestUsedAtlasPage)
{
highestUsedAtlasPage = tile.AtlasLocation.ArrayIndex;
}
}
}
}

View file

@ -13,7 +13,6 @@ public:
void BeginFrame(FLevelLocals& doomMap);
bool TraceSky(const FVector3& start, FVector3 direction, float dist);
void PackLightmapAtlas();
void DumpMesh(const FString& objFilename, const FString& mtlFilename) const;
void BuildSectorGroups(const FLevelLocals& doomMap);

View file

@ -32,9 +32,9 @@ DoomLevelSubmesh::DoomLevelSubmesh(DoomLevelMesh* mesh, FLevelLocals& doomMap, b
LinkSurfaces(doomMap);
CreateIndexes();
SetupLightmapUvs(doomMap);
BuildTileSurfaceLists();
UpdateCollision();
PackLightmapAtlas(0);
}
}
@ -48,7 +48,6 @@ void DoomLevelSubmesh::Update(FLevelLocals& doomMap, int lightmapStartIndex)
LinkSurfaces(doomMap);
CreateIndexes();
SetupLightmapUvs(doomMap);
BuildTileSurfaceLists();
UpdateCollision();
@ -86,6 +85,7 @@ void DoomLevelSubmesh::CreateStaticSurfaces(FLevelLocals& doomMap)
}
MeshBuilder state;
std::map<LightmapTileBinding, int> bindings;
// Create surface objects for all sides
for (unsigned int i = 0; i < doomMap.sides.Size(); i++)
@ -115,19 +115,19 @@ void DoomLevelSubmesh::CreateStaticSurfaces(FLevelLocals& doomMap)
state.EnableTexture(true);
state.EnableBrightmap(true);
state.AlphaFunc(Alpha_GEqual, 0.f);
CreateWallSurface(side, disp, state, result.list, false, true);
CreateWallSurface(side, disp, state, bindings, result.list, false, true);
for (HWWall& portal : result.portals)
{
Portals.Push(portal);
}
CreateWallSurface(side, disp, state, result.portals, true, false);
CreateWallSurface(side, disp, state, bindings, result.portals, true, false);
// final pass: translucent stuff
state.AlphaFunc(Alpha_GEqual, gl_mask_sprite_threshold);
state.SetRenderStyle(STYLE_Translucent);
CreateWallSurface(side, disp, state, result.translucent, false, true);
CreateWallSurface(side, disp, state, bindings, result.translucent, false, true);
state.AlphaFunc(Alpha_GEqual, 0.f);
state.SetRenderStyle(STYLE_Normal);
}
@ -154,24 +154,29 @@ void DoomLevelSubmesh::CreateStaticSurfaces(FLevelLocals& doomMap)
state.ClearDepthBias();
state.EnableTexture(true);
state.EnableBrightmap(true);
CreateFlatSurface(disp, state, result.list);
CreateFlatSurface(disp, state, bindings, result.list);
CreateFlatSurface(disp, state, result.portals, true);
CreateFlatSurface(disp, state, bindings, result.portals, true);
// final pass: translucent stuff
state.AlphaFunc(Alpha_GEqual, gl_mask_sprite_threshold);
state.SetRenderStyle(STYLE_Translucent);
CreateFlatSurface(disp, state, result.translucentborder);
CreateFlatSurface(disp, state, bindings, result.translucentborder);
state.SetDepthMask(false);
CreateFlatSurface(disp, state, result.translucent);
CreateFlatSurface(disp, state, bindings, result.translucent);
state.AlphaFunc(Alpha_GEqual, 0.f);
state.SetDepthMask(true);
state.SetRenderStyle(STYLE_Normal);
}
}
for (auto& tile : LightmapTiles)
{
SetupTileTransform(LMTextureSize, LMTextureSize, tile);
}
}
void DoomLevelSubmesh::CreateWallSurface(side_t* side, HWWallDispatcher& disp, MeshBuilder& state, TArray<HWWall>& list, bool isSky, bool translucent)
void DoomLevelSubmesh::CreateWallSurface(side_t* side, HWWallDispatcher& disp, MeshBuilder& state, std::map<LightmapTileBinding, int>& bindings, TArray<HWWall>& list, bool isSky, bool translucent)
{
for (HWWall& wallpart : list)
{
@ -254,11 +259,74 @@ void DoomLevelSubmesh::CreateWallSurface(side_t* side, HWWallDispatcher& disp, M
surf.PipelineID = pipelineID;
surf.PortalIndex = isSky ? LevelMesh->linePortals[side->linedef->Index()] : 0;
surf.IsSky = isSky;
surf.Bounds = GetBoundsFromSurface(surf);
surf.LightmapTileIndex = AddSurfaceToTile(surf, bindings);
Surfaces.Push(surf);
}
}
void DoomLevelSubmesh::CreateFlatSurface(HWFlatDispatcher& disp, MeshBuilder& state, TArray<HWFlat>& list, bool isSky)
int DoomLevelSubmesh::AddSurfaceToTile(const DoomLevelMeshSurface& surf, std::map<LightmapTileBinding, int>& bindings)
{
LightmapTileBinding binding;
binding.Type = surf.Type;
binding.TypeIndex = surf.TypeIndex;
binding.ControlSector = surf.ControlSector ? surf.ControlSector->Index() : (int)0xffffffffUL;
auto it = bindings.find(binding);
if (it != bindings.end())
{
int index = it->second;
LightmapTile& tile = LightmapTiles[index];
tile.Bounds.min.X = std::min(tile.Bounds.min.X, surf.Bounds.min.X);
tile.Bounds.min.Y = std::min(tile.Bounds.min.Y, surf.Bounds.min.Y);
tile.Bounds.min.Z = std::min(tile.Bounds.min.Z, surf.Bounds.min.Z);
tile.Bounds.max.X = std::max(tile.Bounds.max.X, surf.Bounds.max.X);
tile.Bounds.max.Y = std::max(tile.Bounds.max.Y, surf.Bounds.max.Y);
tile.Bounds.max.Z = std::max(tile.Bounds.max.Z, surf.Bounds.max.Z);
return index;
}
else
{
int index = LightmapTiles.Size();
LightmapTile tile;
tile.Binding = binding;
tile.Bounds = surf.Bounds;
tile.Plane = surf.Plane;
tile.SampleDimension = GetSampleDimension(surf);
LightmapTiles.Push(tile);
bindings[binding] = index;
return index;
}
}
int DoomLevelSubmesh::GetSampleDimension(const DoomLevelMeshSurface& surf)
{
uint16_t sampleDimension = 0; // To do: something seems to have gone missing with the sample dimension!
if (sampleDimension <= 0)
{
sampleDimension = LightmapSampleDistance;
}
sampleDimension = uint16_t(max(int(roundf(float(sampleDimension) / max(1.0f / 4, float(lm_scale)))), 1));
// Round to nearest power of two
uint32_t n = uint16_t(sampleDimension);
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
n |= n >> 8;
n = (n + 1) >> 1;
sampleDimension = uint16_t(n) ? uint16_t(n) : uint16_t(0xFFFF);
return sampleDimension;
}
void DoomLevelSubmesh::CreateFlatSurface(HWFlatDispatcher& disp, MeshBuilder& state, std::map<LightmapTileBinding, int>& bindings, TArray<HWFlat>& list, bool isSky)
{
for (HWFlat& flatpart : list)
{
@ -355,6 +423,8 @@ void DoomLevelSubmesh::CreateFlatSurface(HWFlatDispatcher& disp, MeshBuilder& st
surf.Subsector = sub;
surf.MeshLocation.StartVertIndex = startVertIndex;
surf.MeshLocation.NumVerts = sub->numlines;
surf.Bounds = GetBoundsFromSurface(surf);
surf.LightmapTileIndex = AddSurfaceToTile(surf, bindings);
Surfaces.Push(surf);
}
}
@ -553,54 +623,52 @@ bool DoomLevelSubmesh::IsDegenerate(const FVector3 &v0, const FVector3 &v1, cons
return crosslengthsqr <= 1.e-6f;
}
void DoomLevelSubmesh::SetupLightmapUvs(FLevelLocals& doomMap)
{
LMTextureSize = 1024;
for (auto& surface : Surfaces)
{
SetupTileTransform(LMTextureSize, LMTextureSize, surface);
}
}
void DoomLevelSubmesh::PackLightmapAtlas(int lightmapStartIndex)
{
std::vector<LevelMeshSurface*> sortedSurfaces;
sortedSurfaces.reserve(Surfaces.Size());
std::vector<LightmapTile*> sortedTiles;
sortedTiles.reserve(LightmapTiles.Size());
for (auto& surface : Surfaces)
for (auto& tile : LightmapTiles)
{
sortedSurfaces.push_back(&surface);
sortedTiles.push_back(&tile);
}
std::sort(sortedSurfaces.begin(), sortedSurfaces.end(), [](LevelMeshSurface* a, LevelMeshSurface* b) { return a->AtlasTile.Height != b->AtlasTile.Height ? a->AtlasTile.Height > b->AtlasTile.Height : a->AtlasTile.Width > b->AtlasTile.Width; });
std::sort(sortedTiles.begin(), sortedTiles.end(), [](LightmapTile* a, LightmapTile* b) { return a->AtlasLocation.Height != b->AtlasLocation.Height ? a->AtlasLocation.Height > b->AtlasLocation.Height : a->AtlasLocation.Width > b->AtlasLocation.Width; });
RectPacker packer(LMTextureSize, LMTextureSize, RectPacker::Spacing(0));
for (LevelMeshSurface* surf : sortedSurfaces)
for (LightmapTile* tile : sortedTiles)
{
int sampleWidth = surf->AtlasTile.Width;
int sampleHeight = surf->AtlasTile.Height;
int sampleWidth = tile->AtlasLocation.Width;
int sampleHeight = tile->AtlasLocation.Height;
auto result = packer.insert(sampleWidth, sampleHeight);
int x = result.pos.x, y = result.pos.y;
surf->AtlasTile.X = x;
surf->AtlasTile.Y = y;
surf->AtlasTile.ArrayIndex = lightmapStartIndex + (int)result.pageIndex;
// calculate final texture coordinates
for (int i = 0; i < (int)surf->MeshLocation.NumVerts; i++)
{
auto& vertex = Mesh.Vertices[surf->MeshLocation.StartVertIndex + i];
vertex.lu = (vertex.lu + x) / (float)LMTextureSize;
vertex.lv = (vertex.lv + y) / (float)LMTextureSize;
vertex.lindex = (float)surf->AtlasTile.ArrayIndex;
}
tile->AtlasLocation.X = x;
tile->AtlasLocation.Y = y;
tile->AtlasLocation.ArrayIndex = lightmapStartIndex + (int)result.pageIndex;
}
LMTextureCount = (int)packer.getNumPages();
// Calculate final texture coordinates
for (auto& surface : Surfaces)
{
if (surface.LightmapTileIndex >= 0)
{
const LightmapTile& tile = LightmapTiles[surface.LightmapTileIndex];
for (int i = 0; i < surface.MeshLocation.NumVerts; i++)
{
FVector3 tDelta = Mesh.Vertices[surface.MeshLocation.StartVertIndex + i].fPos() - tile.Transform.TranslateWorldToLocal;
auto& vertex = Mesh.Vertices[surface.MeshLocation.StartVertIndex + i];
vertex.lu = (tile.AtlasLocation.X + (tDelta | tile.Transform.ProjLocalToU)) / (float)LMTextureSize;
vertex.lv = (tile.AtlasLocation.Y + (tDelta | tile.Transform.ProjLocalToV)) / (float)LMTextureSize;
vertex.lindex = (float)tile.AtlasLocation.ArrayIndex;
}
}
}
#if 0 // Debug atlas tile locations:
uint16_t colors[30] =
{
@ -617,21 +685,20 @@ void DoomLevelSubmesh::PackLightmapAtlas(int lightmapStartIndex)
};
LMTextureData.Resize(LMTextureSize * LMTextureSize * LMTextureCount * 3);
uint16_t* pixels = LMTextureData.Data();
for (DoomLevelMeshSurface& surf : Surfaces)
for (LightmapTile& tile : LightmapTiles)
{
surf.AlwaysUpdate = false;
surf.NeedsUpdate = false;
tile.NeedsUpdate = false;
int index = surf.Side ? surf.Side->Index() : (surf.Subsector && surf.Subsector->sector ? surf.Subsector->sector->Index() : 0);
int index = tile.Binding.TypeIndex;
uint16_t* color = colors + (index % 10) * 3;
int x = surf.AtlasTile.X;
int y = surf.AtlasTile.Y;
int w = surf.AtlasTile.Width;
int h = surf.AtlasTile.Height;
int x = tile.AtlasLocation.X;
int y = tile.AtlasLocation.Y;
int w = tile.AtlasLocation.Width;
int h = tile.AtlasLocation.Height;
for (int yy = y; yy < y + h; yy++)
{
uint16_t* line = pixels + surf.AtlasTile.ArrayIndex * LMTextureSize * LMTextureSize + yy * LMTextureSize * 3;
uint16_t* line = pixels + tile.AtlasLocation.ArrayIndex * LMTextureSize * LMTextureSize + yy * LMTextureSize * 3;
for (int xx = x; xx < x + w; xx++)
{
line[xx * 3] = color[0];
@ -640,35 +707,31 @@ void DoomLevelSubmesh::PackLightmapAtlas(int lightmapStartIndex)
}
}
}
for (DoomLevelMeshSurface& surf : Surfaces)
{
surf.AlwaysUpdate = false;
}
#endif
}
BBox DoomLevelSubmesh::GetBoundsFromSurface(const LevelMeshSurface& surface) const
{
constexpr float M_INFINITY = 1e30f; // TODO cleanup
FVector3 low(M_INFINITY, M_INFINITY, M_INFINITY);
FVector3 hi(-M_INFINITY, -M_INFINITY, -M_INFINITY);
BBox bounds;
bounds.Clear();
for (int i = int(surface.MeshLocation.StartVertIndex); i < int(surface.MeshLocation.StartVertIndex) + surface.MeshLocation.NumVerts; i++)
{
for (int j = 0; j < 3; j++)
{
if (Mesh.Vertices[i].fPos()[j] < low[j])
if (Mesh.Vertices[i].fPos()[j] < bounds.min[j])
{
low[j] = Mesh.Vertices[i].fPos()[j];
bounds.min[j] = Mesh.Vertices[i].fPos()[j];
}
if (Mesh.Vertices[i].fPos()[j] > hi[j])
if (Mesh.Vertices[i].fPos()[j] > bounds.max[j])
{
hi[j] = Mesh.Vertices[i].fPos()[j];
bounds.max[j] = Mesh.Vertices[i].fPos()[j];
}
}
}
BBox bounds;
bounds.Clear();
bounds.min = low;
bounds.max = hi;
return bounds;
}
@ -691,41 +754,22 @@ DoomLevelSubmesh::PlaneAxis DoomLevelSubmesh::BestAxis(const FVector4& p)
return AXIS_XY;
}
void DoomLevelSubmesh::SetupTileTransform(int lightMapTextureWidth, int lightMapTextureHeight, LevelMeshSurface& surface)
void DoomLevelSubmesh::SetupTileTransform(int lightMapTextureWidth, int lightMapTextureHeight, LightmapTile& tile)
{
BBox bounds = GetBoundsFromSurface(surface);
surface.Bounds = bounds;
if (surface.SampleDimension <= 0)
{
surface.SampleDimension = LightmapSampleDistance;
}
surface.SampleDimension = uint16_t(max(int(roundf(float(surface.SampleDimension) / max(1.0f / 4, float(lm_scale)))), 1));
{
// Round to nearest power of two
uint32_t n = uint16_t(surface.SampleDimension);
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
n |= n >> 8;
n = (n + 1) >> 1;
surface.SampleDimension = uint16_t(n) ? uint16_t(n) : uint16_t(0xFFFF);
}
BBox bounds = tile.Bounds;
// round off dimensions
FVector3 roundedSize;
for (int i = 0; i < 3; i++)
{
bounds.min[i] = surface.SampleDimension * (floor(bounds.min[i] / surface.SampleDimension) - 1);
bounds.max[i] = surface.SampleDimension * (ceil(bounds.max[i] / surface.SampleDimension) + 1);
roundedSize[i] = (bounds.max[i] - bounds.min[i]) / surface.SampleDimension;
bounds.min[i] = tile.SampleDimension * (floor(bounds.min[i] / tile.SampleDimension) - 1);
bounds.max[i] = tile.SampleDimension * (ceil(bounds.max[i] / tile.SampleDimension) + 1);
roundedSize[i] = (bounds.max[i] - bounds.min[i]) / tile.SampleDimension;
}
FVector3 tCoords[2] = { FVector3(0.0f, 0.0f, 0.0f), FVector3(0.0f, 0.0f, 0.0f) };
PlaneAxis axis = BestAxis(surface.Plane);
PlaneAxis axis = BestAxis(tile.Plane);
int width;
int height;
@ -735,22 +779,22 @@ void DoomLevelSubmesh::SetupTileTransform(int lightMapTextureWidth, int lightMap
case AXIS_YZ:
width = (int)roundedSize.Y;
height = (int)roundedSize.Z;
tCoords[0].Y = 1.0f / surface.SampleDimension;
tCoords[1].Z = 1.0f / surface.SampleDimension;
tCoords[0].Y = 1.0f / tile.SampleDimension;
tCoords[1].Z = 1.0f / tile.SampleDimension;
break;
case AXIS_XZ:
width = (int)roundedSize.X;
height = (int)roundedSize.Z;
tCoords[0].X = 1.0f / surface.SampleDimension;
tCoords[1].Z = 1.0f / surface.SampleDimension;
tCoords[0].X = 1.0f / tile.SampleDimension;
tCoords[1].Z = 1.0f / tile.SampleDimension;
break;
case AXIS_XY:
width = (int)roundedSize.X;
height = (int)roundedSize.Y;
tCoords[0].X = 1.0f / surface.SampleDimension;
tCoords[1].Y = 1.0f / surface.SampleDimension;
tCoords[0].X = 1.0f / tile.SampleDimension;
tCoords[1].Y = 1.0f / tile.SampleDimension;
break;
}
@ -768,18 +812,10 @@ void DoomLevelSubmesh::SetupTileTransform(int lightMapTextureWidth, int lightMap
height = (lightMapTextureHeight - 2);
}
surface.TileTransform.TranslateWorldToLocal = bounds.min;
surface.TileTransform.ProjLocalToU = tCoords[0];
surface.TileTransform.ProjLocalToV = tCoords[1];
tile.Transform.TranslateWorldToLocal = bounds.min;
tile.Transform.ProjLocalToU = tCoords[0];
tile.Transform.ProjLocalToV = tCoords[1];
for (int i = 0; i < surface.MeshLocation.NumVerts; i++)
{
FVector3 tDelta = Mesh.Vertices[surface.MeshLocation.StartVertIndex + i].fPos() - surface.TileTransform.TranslateWorldToLocal;
Mesh.Vertices[surface.MeshLocation.StartVertIndex + i].lu = (tDelta | surface.TileTransform.ProjLocalToU);
Mesh.Vertices[surface.MeshLocation.StartVertIndex + i].lv = (tDelta | surface.TileTransform.ProjLocalToV);
}
surface.AtlasTile.Width = width;
surface.AtlasTile.Height = height;
tile.AtlasLocation.Width = width;
tile.AtlasLocation.Height = height;
}

View file

@ -9,6 +9,7 @@
#include "bounds.h"
#include <dp_rect_pack.h>
#include <set>
#include <map>
typedef dp::rect_pack::RectPacker<int> RectPacker;
@ -60,12 +61,10 @@ private:
void SetSubsectorLightmap(DoomLevelMeshSurface* surface);
void SetSideLightmap(DoomLevelMeshSurface* surface);
void SetupLightmapUvs(FLevelLocals& doomMap);
void CreateIndexes();
void CreateWallSurface(side_t* side, HWWallDispatcher& disp, MeshBuilder& state, TArray<HWWall>& list, bool isSky, bool translucent);
void CreateFlatSurface(HWFlatDispatcher& disp, MeshBuilder& state, TArray<HWFlat>& list, bool isSky = false);
void CreateWallSurface(side_t* side, HWWallDispatcher& disp, MeshBuilder& state, std::map<LightmapTileBinding, int>& bindings, TArray<HWWall>& list, bool isSky, bool translucent);
void CreateFlatSurface(HWFlatDispatcher& disp, MeshBuilder& state, std::map<LightmapTileBinding, int>& bindings, TArray<HWFlat>& list, bool isSky = false);
static FVector4 ToPlane(const FVector3& pt1, const FVector3& pt2, const FVector3& pt3)
{
@ -100,7 +99,9 @@ private:
static PlaneAxis BestAxis(const FVector4& p);
BBox GetBoundsFromSurface(const LevelMeshSurface& surface) const;
void SetupTileTransform(int lightMapTextureWidth, int lightMapTextureHeight, LevelMeshSurface& surface);
void SetupTileTransform(int lightMapTextureWidth, int lightMapTextureHeight, LightmapTile& tile);
int AddSurfaceToTile(const DoomLevelMeshSurface& surf, std::map<LightmapTileBinding, int>& bindings);
int GetSampleDimension(const DoomLevelMeshSurface& surf);
static bool IsDegenerate(const FVector3& v0, const FVector3& v1, const FVector3& v2);

View file

@ -129,7 +129,7 @@ void HWDrawInfo::StartScene(FRenderViewpoint &parentvp, HWViewpointUniforms *uni
hudsprites.Clear();
Coronas.Clear();
Fogballs.Clear();
VisibleSurfaces.Clear();
VisibleTiles.Clear();
vpIndex = 0;
// Fullbright information needs to be propagated from the main view.
@ -620,20 +620,20 @@ void HWDrawInfo::PutWallPortal(HWWall wall, FRenderState& state)
void HWDrawInfo::UpdateLightmaps()
{
if (!outer && VisibleSurfaces.Size() < unsigned(lm_background_updates))
if (!outer && VisibleTiles.Size() < unsigned(lm_background_updates))
{
for (auto& e : static_cast<DoomLevelSubmesh*>(level.levelMesh->StaticMesh.get())->Surfaces)
for (auto& e : level.levelMesh->StaticMesh->LightmapTiles)
{
if (e.NeedsUpdate && !e.IsSky && !e.PortalIndex)
if (e.NeedsUpdate)
{
VisibleSurfaces.Push(&e);
VisibleTiles.Push(&e);
if (VisibleSurfaces.Size() >= unsigned(lm_background_updates))
if (VisibleTiles.Size() >= unsigned(lm_background_updates))
break;
}
}
}
screen->UpdateLightmaps(VisibleSurfaces);
screen->UpdateLightmaps(VisibleTiles);
}
//-----------------------------------------------------------------------------

View file

@ -153,7 +153,7 @@ struct HWDrawInfo
TArray<HUDSprite> hudsprites; // These may just be stored by value.
TArray<AActor*> Coronas;
TArray<Fogball> Fogballs;
TArray<LevelMeshSurface*> VisibleSurfaces;
TArray<LightmapTile*> VisibleTiles;
uint64_t LastFrameTime = 0;
TArray<MissingTextureInfo> MissingUpperTextures;
@ -232,18 +232,22 @@ public:
return;
}
if (surface->LightmapTileIndex < 0)
return;
LightmapTile* tile = &surface->Submesh->LightmapTiles[surface->LightmapTileIndex];
if (lm_always_update || surface->AlwaysUpdate)
{
surface->NeedsUpdate = true;
tile->NeedsUpdate = true;
}
else if (VisibleSurfaces.Size() >= unsigned(lm_max_updates))
else if (VisibleTiles.Size() >= unsigned(lm_max_updates))
{
return;
}
if (surface->NeedsUpdate && !surface->PortalIndex && !surface->IsSky)
if (tile->NeedsUpdate)
{
VisibleSurfaces.Push(surface);
VisibleTiles.Push(tile);
}
}

View file

@ -11,10 +11,10 @@ struct SurfaceInfo
{
vec3 Normal;
float Sky;
float SamplingDistance;
uint PortalIndex;
int TextureIndex;
float Alpha;
float Padding;
};
struct PortalInfo