diff --git a/CMakeLists.txt b/CMakeLists.txt index 12095b4..5786f90 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,8 +69,6 @@ set(ZDRAY_SOURCES src/lightmapper/vk_lightmapper.h src/lightmapper/doom_levelmesh.cpp src/lightmapper/doom_levelmesh.h - src/lightmapper/doom_levelsubmesh.cpp - src/lightmapper/doom_levelsubmesh.h src/lightmapper/gpuraytracer.cpp src/lightmapper/gpuraytracer.h src/lightmapper/stacktrace.cpp @@ -82,8 +80,10 @@ set(ZDRAY_SOURCES src/lightmapper/glsl/frag_copy.glsl.h src/lightmapper/glsl/frag_raytrace.glsl.h src/lightmapper/glsl/frag_resolve.glsl.h + src/lightmapper/glsl/montecarlo.glsl.h src/lightmapper/glsl/polyfill_rayquery.glsl.h src/lightmapper/glsl/trace_ambient_occlusion.glsl.h + src/lightmapper/glsl/trace_bounce.glsl.h src/lightmapper/glsl/trace_levelmesh.glsl.h src/lightmapper/glsl/trace_light.glsl.h src/lightmapper/glsl/trace_sunlight.glsl.h diff --git a/src/level/doomdata.h b/src/level/doomdata.h index c204a84..c2cc904 100644 --- a/src/level/doomdata.h +++ b/src/level/doomdata.h @@ -414,6 +414,14 @@ struct ThingLight IntSector *sector; MapSubsectorEx *ssect; + // Locations in the level mesh light list. Ends with index = 0 or all entries used + enum { max_levelmesh_entries = 4 }; + struct + { + int index = 0; + int portalgroup = 0; + } levelmesh[max_levelmesh_entries]; + // Portal related functionality std::optional relativePosition; int sectorGroup = 0; diff --git a/src/level/level.cpp b/src/level/level.cpp index a9ad6a5..7472e6b 100644 --- a/src/level/level.cpp +++ b/src/level/level.cpp @@ -841,8 +841,11 @@ void FProcessor::BuildLightmaps() printf(" Creating level mesh\n"); LightmapMesh = std::make_unique(Level); - printf(" Surfaces: %d\n", LightmapMesh->StaticMesh->GetSurfaceCount()); - printf(" Tiles: %d\n", (int)LightmapMesh->StaticMesh->LightmapTiles.Size()); + LightmapMesh->SetupTileTransforms(); + LightmapMesh->PackLightmapAtlas(0); + LightmapMesh->BeginFrame(Level); + printf(" Surfaces: %d\n", LightmapMesh->GetSurfaceCount()); + printf(" Tiles: %d\n", (int)LightmapMesh->LightmapTiles.Size()); std::unique_ptr gpuraytracer = std::make_unique(); gpuraytracer->Raytrace(LightmapMesh.get()); diff --git a/src/lightmapper/doom_levelmesh.cpp b/src/lightmapper/doom_levelmesh.cpp index 046d9dc..43d5acc 100644 --- a/src/lightmapper/doom_levelmesh.cpp +++ b/src/lightmapper/doom_levelmesh.cpp @@ -1,30 +1,168 @@ #include "doom_levelmesh.h" -#include "doom_levelsubmesh.h" #include "level/level.h" #include "framework/halffloat.h" #include "framework/binfile.h" #include #include #include +#include + +extern float lm_scale; DoomLevelMesh::DoomLevelMesh(FLevel& doomMap) { + // Remove the empty mesh added in the LevelMesh constructor + Mesh.Vertices.Clear(); + Mesh.Indexes.Clear(); + SunColor = doomMap.defaultSunColor; // TODO keep only one copy? SunDirection = doomMap.defaultSunDirection; BuildSectorGroups(doomMap); CreatePortals(doomMap); - StaticMesh = std::make_unique(this, doomMap, true); - DynamicMesh = std::make_unique(this, doomMap, false); + LightmapSampleDistance = doomMap.DefaultSamples; - BuildLightLists(doomMap); + CreateSurfaces(doomMap); + + SortIndexes(); + BuildTileSurfaceLists(); + + Mesh.DynamicIndexStart = Mesh.Indexes.Size(); + UpdateCollision(); + + // Assume double the size of the static mesh will be enough for anything dynamic. + Mesh.MaxVertices = std::max(Mesh.Vertices.Size() * 2, (unsigned int)10000); + Mesh.MaxIndexes = std::max(Mesh.Indexes.Size() * 2, (unsigned int)10000); + Mesh.MaxSurfaces = std::max(Mesh.SurfaceIndexes.Size() * 2, (unsigned int)10000); + Mesh.MaxUniforms = std::max(Mesh.Uniforms.Size() * 2, (unsigned int)10000); + Mesh.MaxSurfaceIndexes = std::max(Mesh.SurfaceIndexes.Size() * 2, (unsigned int)10000); + Mesh.MaxNodes = (int)std::max(Collision->get_nodes().size() * 2, (size_t)10000); + Mesh.MaxLights = 100'000; + Mesh.MaxLightIndexes = 4 * 1024 * 1024; +} + +void DoomLevelMesh::CreateLights(FLevel& doomMap) +{ + if (Mesh.Lights.Size() != 0) + return; + + for (unsigned i = 0; i < doomMap.ThingLights.Size(); ++i) + { + printf(" Building light lists: %u / %u\r", i, doomMap.ThingLights.Size()); + PropagateLight(doomMap, &doomMap.ThingLights[i], 0); + } + + printf(" Building light lists: %u / %u\n", doomMap.ThingLights.Size(), doomMap.ThingLights.Size()); + + for (DoomLevelMeshSurface& surface : Surfaces) + { + surface.LightList.Pos = Mesh.LightIndexes.Size(); + surface.LightList.Count = 0; + + int listpos = 0; + for (ThingLight* light : surface.Lights) + { + int lightindex = GetLightIndex(light, surface.PortalIndex); + if (lightindex >= 0) + { + Mesh.LightIndexes.Push(lightindex); + surface.LightList.Count++; + } + } + } +} + +void DoomLevelMesh::PropagateLight(FLevel& doomMap, ThingLight* light, int recursiveDepth) +{ + if (recursiveDepth > 32) + return; + + SphereShape sphere; + sphere.center = light->LightRelativeOrigin(); + sphere.radius = light->LightRadius(); + //std::set portalsToErase; + for (int triangleIndex : TriangleMeshShape::find_all_hits(Collision.get(), &sphere)) + { + DoomLevelMeshSurface* surface = &Surfaces[Mesh.SurfaceIndexes[triangleIndex]]; + + // skip any surface which isn't physically connected to the sector group in which the light resides + //if (light->sectorGroup == surface->sectorGroup) + { + /*if (surface->portalIndex >= 0) + { + auto portal = portals[surface->portalIndex].get(); + + if (touchedPortals.insert(*portal).second) + { + auto fakeLight = std::make_unique(*light); + + fakeLight->relativePosition.emplace(portal->TransformPosition(light->LightRelativeOrigin())); + fakeLight->sectorGroup = portal->targetSectorGroup; + + PropagateLight(doomMap, fakeLight.get(), recursiveDepth + 1); + portalsToErase.insert(*portal); + portalLights.push_back(std::move(fakeLight)); + } + }*/ + + // Add light to the list if it isn't already there + bool found = false; + for (ThingLight* light2 : surface->Lights) + { + if (light2 == light) + { + found = true; + break; + } + } + if (!found) + surface->Lights.Push(light); + } + } + + /*for (auto& portal : portalsToErase) + { + touchedPortals.erase(portal); + }*/ +} + +int DoomLevelMesh::GetLightIndex(ThingLight* light, int portalgroup) +{ + int index; + for (index = 0; index < ThingLight::max_levelmesh_entries && light->levelmesh[index].index != 0; index++) + { + if (light->levelmesh[index].portalgroup == portalgroup) + return light->levelmesh[index].index - 1; + } + if (index == ThingLight::max_levelmesh_entries) + return 0; + + LevelMeshLight meshlight; + meshlight.Origin = light->LightOrigin(); // light->PosRelative(portalgroup); + meshlight.RelativeOrigin = light->LightRelativeOrigin(); + meshlight.Radius = light->LightRadius(); + meshlight.Intensity = light->intensity; + meshlight.InnerAngleCos = light->innerAngleCos; + meshlight.OuterAngleCos = light->outerAngleCos; + meshlight.SpotDir = light->SpotDir(); + meshlight.Color = light->rgb; + + meshlight.SectorGroup = 0; + // if (light->sector) + // meshlight.SectorGroup = sectorGroup[light->sector->Index(doomMap)]; + + int lightindex = Mesh.Lights.Size(); + light->levelmesh[index].index = lightindex + 1; + light->levelmesh[index].portalgroup = portalgroup; + Mesh.Lights.Push(meshlight); + return lightindex; } void DoomLevelMesh::BeginFrame(FLevel& doomMap) { - static_cast(DynamicMesh.get())->Update(doomMap, static_cast(StaticMesh.get())->LMTextureCount); + CreateLights(doomMap); } bool DoomLevelMesh::TraceSky(const FVector3& start, FVector3 direction, float dist) @@ -34,196 +172,792 @@ bool DoomLevelMesh::TraceSky(const FVector3& start, FVector3 direction, float di return surface && surface->IsSky; } -int DoomLevelMesh::AddSurfaceLights(const LevelMeshSurface* surface, LevelMeshLight* list, int listMaxSize) +void DoomLevelMesh::CreateSurfaces(FLevel& doomMap) { - const DoomLevelMeshSurface* doomsurface = static_cast(surface); - int listpos = 0; - for (ThingLight* light : doomsurface->LightList) + bindings.clear(); + Sides.Clear(); + Flats.Clear(); + Sides.Resize(doomMap.Sides.Size()); + Flats.Resize(doomMap.Sectors.Size()); + + // Create surface objects for all sides + for (unsigned int i = 0; i < doomMap.Sides.Size(); i++) { - if (listpos == listMaxSize) - break; - - LevelMeshLight& meshlight = list[listpos++]; - meshlight.Origin = light->LightOrigin(); - meshlight.RelativeOrigin = light->LightRelativeOrigin(); - meshlight.Radius = light->LightRadius(); - meshlight.Intensity = light->intensity; - meshlight.InnerAngleCos = light->innerAngleCos; - meshlight.OuterAngleCos = light->outerAngleCos; - meshlight.SpotDir = light->SpotDir(); - meshlight.Color = light->rgb; - - /*if (light->sector) - meshlight.SectorGroup = static_cast(StaticMesh.get())->sectorGroup[light->sector->Index(doomMap)]; - else*/ - meshlight.SectorGroup = 0; + CreateSideSurfaces(doomMap, &doomMap.Sides[i]); + } + + // Create surfaces for all flats + for (int i = 0; i < doomMap.NumGLSubsectors; i++) + { + MapSubsectorEx* sub = &doomMap.GLSubsectors[i]; + + if (sub->numlines < 3) + { + continue; + } + + IntSector* sector = sub->GetSector(doomMap); + if (!sector) + continue; + + CreateFloorSurface(doomMap, sub, sector, nullptr, i); + CreateCeilingSurface(doomMap, sub, sector, nullptr, i); + + for (unsigned int j = 0; j < sector->x3dfloors.Size(); j++) + { + CreateFloorSurface(doomMap, sub, sector, sector->x3dfloors[j], i); + CreateCeilingSurface(doomMap, sub, sector, sector->x3dfloors[j], i); + } } - return listpos; } -void DoomLevelMesh::AddLightmapLump(FLevel& doomMap, FWadWriter& wadFile) +void DoomLevelMesh::CreateSideSurfaces(FLevel& doomMap, IntSideDef* side) { - /* - // LIGHTMAP V2 pseudo-C specification: + IntSector* front = side->sectordef; + IntSector* back = (side->line->frontsector == front) ? side->line->backsector : side->line->frontsector; - struct LightmapLump + FVector2 v1 = side->V1(doomMap); + FVector2 v2 = side->V2(doomMap); + + float v1Top = (float)front->ceilingplane.ZatPoint(v1); + float v1Bottom = (float)front->floorplane.ZatPoint(v1); + float v2Top = (float)front->ceilingplane.ZatPoint(v2); + float v2Bottom = (float)front->floorplane.ZatPoint(v2); + + /*if (side->line->getPortal() && side->line->frontsector == front) { - int version = 2; - uint32_t tileCount; - uint32_t pixelCount; - uint32_t uvCount; - SurfaceEntry surfaces[surfaceCount]; - uint16_t pixels[pixelCount * 3]; - float uvs[uvCount * 2]; - }; - - struct TileEntry + CreateLinePortalSurface(doomMap, side); + } + else*/ if (side->line->special == Line_Horizon && front != back) { - uint32_t type, typeIndex; - uint32_t controlSector; // 0xFFFFFFFF is none - uint16_t width, height; // in pixels - uint32_t pixelsOffset; // offset in pixels array - vec3 translateWorldToLocal; - vec3 projLocalToU; - vec3 projLocalToV; - }; - */ - // Calculate size of lump - uint32_t tileCount = 0; - uint32_t pixelCount = 0; - - auto submesh = static_cast(StaticMesh.get()); - - for (unsigned int i = 0; i < submesh->LightmapTiles.Size(); i++) + CreateLineHorizonSurface(doomMap, side); + } + else if (!back) { - LightmapTile* tile = &submesh->LightmapTiles[i]; - if (tile->AtlasLocation.ArrayIndex != -1) + if (side->GetTexture(WallPart::MIDDLE).isValid()) { - tileCount++; - pixelCount += tile->AtlasLocation.Area(); + CreateFrontWallSurface(doomMap, side); } } - - printf(" Writing %u tiles out of %llu\n", tileCount, (size_t)submesh->LightmapTiles.Size()); - - const int version = 3; - - const uint32_t headerSize = sizeof(int) + 2 * sizeof(uint32_t); - const uint32_t bytesPerTileEntry = sizeof(uint32_t) * 4 + sizeof(uint16_t) * 2 + sizeof(float) * 9; - const uint32_t bytesPerPixel = sizeof(uint16_t) * 3; // F16 RGB - - uint32_t lumpSize = headerSize + tileCount * bytesPerTileEntry + pixelCount * bytesPerPixel; - - bool debug = false; - - if (debug) + else { - printf("Lump size %u bytes\n", lumpSize); - printf("Tiles: %u\nPixels: %u\n", tileCount, pixelCount); - } - - // Setup buffer - std::vector buffer(lumpSize); - BinFile lumpFile; - lumpFile.SetBuffer(buffer.data()); - - // Write header - lumpFile.Write32(version); - lumpFile.Write32(tileCount); - lumpFile.Write32(pixelCount); - - if (debug) - { - printf("--- Saving tiles ---\n"); - } - - // Write tiles - uint32_t pixelsOffset = 0; - - for (unsigned int i = 0; i < submesh->LightmapTiles.Size(); i++) - { - LightmapTile* tile = &submesh->LightmapTiles[i]; - - if (tile->AtlasLocation.ArrayIndex == -1) - continue; - - lumpFile.Write32(tile->Binding.Type); - lumpFile.Write32(tile->Binding.TypeIndex); - lumpFile.Write32(tile->Binding.ControlSector); - - lumpFile.Write16(uint16_t(tile->AtlasLocation.Width)); - lumpFile.Write16(uint16_t(tile->AtlasLocation.Height)); - - lumpFile.Write32(pixelsOffset * 3); - - lumpFile.WriteFloat(tile->Transform.TranslateWorldToLocal.X); - lumpFile.WriteFloat(tile->Transform.TranslateWorldToLocal.Y); - lumpFile.WriteFloat(tile->Transform.TranslateWorldToLocal.Z); - - lumpFile.WriteFloat(tile->Transform.ProjLocalToU.X); - lumpFile.WriteFloat(tile->Transform.ProjLocalToU.Y); - lumpFile.WriteFloat(tile->Transform.ProjLocalToU.Z); - - lumpFile.WriteFloat(tile->Transform.ProjLocalToV.X); - lumpFile.WriteFloat(tile->Transform.ProjLocalToV.Y); - lumpFile.WriteFloat(tile->Transform.ProjLocalToV.Z); - - pixelsOffset += tile->AtlasLocation.Area(); - } - - if (debug) - { - printf("--- Saving pixels ---\n"); - } - - // Write surface pixels - for (unsigned int i = 0; i < submesh->LightmapTiles.Size(); i++) - { - LightmapTile* tile = &submesh->LightmapTiles[i]; - - if (tile->AtlasLocation.ArrayIndex == -1) - continue; - - const uint16_t* pixels = submesh->LMTextureData.Data() + tile->AtlasLocation.ArrayIndex * submesh->LMTextureSize * submesh->LMTextureSize * 4; - int width = tile->AtlasLocation.Width; - int height = tile->AtlasLocation.Height; - for (int y = 0; y < height; y++) + if (side->GetTexture(WallPart::MIDDLE).isValid()) { - const uint16_t* srcline = pixels + (tile->AtlasLocation.X + (tile->AtlasLocation.Y + y) * submesh->LMTextureSize) * 4; - for (int x = 0; x < width; x++) + CreateMidWallSurface(doomMap, side); + } + + Create3DFloorWallSurfaces(doomMap, side); + + float v1TopBack = (float)back->ceilingplane.ZatPoint(v1); + float v1BottomBack = (float)back->floorplane.ZatPoint(v1); + float v2TopBack = (float)back->ceilingplane.ZatPoint(v2); + float v2BottomBack = (float)back->floorplane.ZatPoint(v2); + + if (v1Bottom < v1BottomBack || v2Bottom < v2BottomBack) + { + CreateBottomWallSurface(doomMap, side); + } + + if (v1Top > v1TopBack || v2Top > v2TopBack) + { + CreateTopWallSurface( doomMap, side); + } + } +} + +void DoomLevelMesh::AddWallVertices(DoomLevelMeshSurface& surf, FFlatVertex* verts) +{ + surf.MeshLocation.StartVertIndex = Mesh.Vertices.Size(); + surf.MeshLocation.StartElementIndex = Mesh.Indexes.Size(); + surf.MeshLocation.NumVerts = 4; + surf.MeshLocation.NumElements = 6; + surf.Plane = ToPlane(verts[2], verts[0], verts[3], verts[1]); + + Mesh.Vertices.Push(verts[0]); + Mesh.Vertices.Push(verts[1]); + Mesh.Vertices.Push(verts[2]); + Mesh.Vertices.Push(verts[3]); + + unsigned int startVertIndex = surf.MeshLocation.StartVertIndex; + Mesh.Indexes.Push(startVertIndex + 0); + Mesh.Indexes.Push(startVertIndex + 1); + Mesh.Indexes.Push(startVertIndex + 2); + Mesh.Indexes.Push(startVertIndex + 3); + Mesh.Indexes.Push(startVertIndex + 2); + Mesh.Indexes.Push(startVertIndex + 1); + + surf.Bounds = GetBoundsFromSurface(surf); +} + +void DoomLevelMesh::CreateLineHorizonSurface(FLevel& doomMap, IntSideDef* side) +{ + IntSector* front = side->sectordef; + + FVector2 v1 = side->V1(doomMap); + FVector2 v2 = side->V2(doomMap); + + float v1Top = (float)front->ceilingplane.ZatPoint(v1); + float v1Bottom = (float)front->floorplane.ZatPoint(v1); + float v2Top = (float)front->ceilingplane.ZatPoint(v2); + float v2Bottom = (float)front->floorplane.ZatPoint(v2); + + DoomLevelMeshSurface surf; + surf.Type = ST_MIDDLESIDE; + surf.TypeIndex = side->Index(doomMap); + surf.Side = side; + surf.IsSky = front->skyFloor || front->skyCeiling; // front->GetTexture(PLANE_FLOOR) == skyflatnum || front->GetTexture(PLANE_CEILING) == skyflatnum; + surf.SectorGroup = sectorGroup[front->Index(doomMap)]; + + FFlatVertex verts[4]; + verts[0].x = verts[2].x = v1.X; + verts[0].y = verts[2].y = v1.Y; + verts[1].x = verts[3].x = v2.X; + verts[1].y = verts[3].y = v2.Y; + verts[0].z = v1Bottom; + verts[1].z = v2Bottom; + verts[2].z = v1Top; + verts[3].z = v2Top; + AddWallVertices(surf, verts); + + SetSideTextureUVs(surf, side, WallPart::TOP, v1Top, v1Bottom, v2Top, v2Bottom); + + Surfaces.Push(surf); +} + +void DoomLevelMesh::CreateFrontWallSurface(FLevel& doomMap, IntSideDef* side) +{ + IntSector* front = side->sectordef; + + FVector2 v1 = side->V1(doomMap); + FVector2 v2 = side->V2(doomMap); + + float v1Top = (float)front->ceilingplane.ZatPoint(v1); + float v1Bottom = (float)front->floorplane.ZatPoint(v1); + float v2Top = (float)front->ceilingplane.ZatPoint(v2); + float v2Bottom = (float)front->floorplane.ZatPoint(v2); + + FFlatVertex verts[4]; + verts[0].x = verts[2].x = v1.X; + verts[0].y = verts[2].y = v1.Y; + verts[1].x = verts[3].x = v2.X; + verts[1].y = verts[3].y = v2.Y; + verts[0].z = v1Bottom; + verts[1].z = v2Bottom; + verts[2].z = v1Top; + verts[3].z = v2Top; + + DoomLevelMeshSurface surf; + surf.Side = side; + surf.IsSky = false; + surf.Type = ST_MIDDLESIDE; + surf.TypeIndex = side->Index(doomMap); + surf.ControlSector = nullptr; + surf.SectorGroup = sectorGroup[front->Index(doomMap)]; + surf.Texture = side->GetTexture(WallPart::MIDDLE); + AddWallVertices(surf, verts); + + SetSideTextureUVs(surf, side, WallPart::TOP, v1Top, v1Bottom, v2Top, v2Bottom); + AddSurfaceToTile(surf, doomMap, side->GetSampleDistance(WallPart::MIDDLE)); + + Surfaces.Push(surf); +} + +void DoomLevelMesh::CreateMidWallSurface(FLevel& doomMap, IntSideDef* side) +{ + IntSector* front = side->sectordef; + + FVector2 v1 = side->V1(doomMap); + FVector2 v2 = side->V2(doomMap); + + float v1Top = (float)front->ceilingplane.ZatPoint(v1); + float v1Bottom = (float)front->floorplane.ZatPoint(v1); + float v2Top = (float)front->ceilingplane.ZatPoint(v2); + float v2Bottom = (float)front->floorplane.ZatPoint(v2); + + FFlatVertex verts[4]; + verts[0].x = verts[2].x = v1.X; + verts[0].y = verts[2].y = v1.Y; + verts[1].x = verts[3].x = v2.X; + verts[1].y = verts[3].y = v2.Y; + + const auto& texture = side->GetTexture(WallPart::MIDDLE); + + //if ((side->Flags & WALLF_WRAP_MIDTEX) || (side->line->flags & WALLF_WRAP_MIDTEX)) + { + verts[0].z = v1Bottom; + verts[1].z = v2Bottom; + verts[2].z = v1Top; + verts[3].z = v2Top; + } + /*else + { + int offset = 0; + + auto gameTexture = TexMan.GetGameTexture(texture); + + float mid1Top = (float)(gameTexture->GetDisplayHeight() / side->GetTextureYScale(WallPart::MIDDLE)); + float mid2Top = (float)(gameTexture->GetDisplayHeight() / side->GetTextureYScale(WallPart::MIDDLE)); + float mid1Bottom = 0; + float mid2Bottom = 0; + + float yTextureOffset = (float)(side->GetTextureYOffset(WallPart::MIDDLE) / gameTexture->GetScaleY()); + + if (side->line->flags & ML_DONTPEGBOTTOM) + { + yTextureOffset += (float)side->sectordef->planes[PLANE_FLOOR].TexZ; + } + else + { + yTextureOffset += (float)(side->sectordef->planes[PLANE_CEILING].TexZ - gameTexture->GetDisplayHeight() / side->GetTextureYScale(WallPart::MIDDLE)); + } + + verts[0].z = std::min(std::max(yTextureOffset + mid1Bottom, v1Bottom), v1Top); + verts[1].z = std::min(std::max(yTextureOffset + mid2Bottom, v2Bottom), v2Top); + verts[2].z = std::max(std::min(yTextureOffset + mid1Top, v1Top), v1Bottom); + verts[3].z = std::max(std::min(yTextureOffset + mid2Top, v2Top), v2Bottom); + }*/ + + // mid texture + DoomLevelMeshSurface surf; + surf.Side = side; + surf.IsSky = false; + surf.Type = ST_MIDDLESIDE; + surf.TypeIndex = side->Index(doomMap); + surf.ControlSector = nullptr; + surf.SectorGroup = sectorGroup[front->Index(doomMap)]; + surf.Texture = texture; + // surf.alpha = float(side->line->alpha); + + // FVector3 offset = surf.Plane.XYZ() * 0.05f; // for better accuracy when raytracing mid-textures from each side + AddWallVertices(surf, verts/*, offset*/); + if (side->line->sidenum[0] != side->Index(doomMap)) + { + surf.Plane = -surf.Plane; + surf.Plane.W = -surf.Plane.W; + } + + SetSideTextureUVs(surf, side, WallPart::TOP, verts[2].z, verts[0].z, verts[3].z, verts[1].z); + AddSurfaceToTile(surf, doomMap, side->GetSampleDistance(WallPart::MIDDLE)); + + Surfaces.Push(surf); +} + +void DoomLevelMesh::Create3DFloorWallSurfaces(FLevel& doomMap, IntSideDef* side) +{ + IntSector* front = side->sectordef; + IntSector* back = (side->line->frontsector == front) ? side->line->backsector : side->line->frontsector; + + FVector2 v1 = side->V1(doomMap); + FVector2 v2 = side->V2(doomMap); + + float v1Top = (float)back->ceilingplane.ZatPoint(v1); + float v1Bottom = (float)back->floorplane.ZatPoint(v1); + float v2Top = (float)back->ceilingplane.ZatPoint(v2); + float v2Bottom = (float)back->floorplane.ZatPoint(v2); + + for (unsigned int j = 0; j < back->x3dfloors.Size(); j++) + { + IntSector* xfloor = back->x3dfloors[j]; + + // Don't create a line when both sectors have the same 3d floor + bool bothSides = false; + for (unsigned int k = 0; k < front->x3dfloors.Size(); k++) + { + if (front->x3dfloors[k] == xfloor) { - lumpFile.Write16(*(srcline++)); - lumpFile.Write16(*(srcline++)); - lumpFile.Write16(*(srcline++)); - srcline++; + bothSides = true; + break; } } + if (bothSides) + continue; + + DoomLevelMeshSurface surf; + surf.Type = ST_MIDDLESIDE; + surf.TypeIndex = side->Index(doomMap); + surf.Side = side; + surf.ControlSector = xfloor; + surf.IsSky = false; + + float blZ = (float)xfloor->floorplane.ZatPoint(v1); + float brZ = (float)xfloor->floorplane.ZatPoint(v2); + float tlZ = (float)xfloor->ceilingplane.ZatPoint(v1); + float trZ = (float)xfloor->ceilingplane.ZatPoint(v2); + + FFlatVertex verts[4]; + verts[0].x = verts[2].x = v2.X; + verts[0].y = verts[2].y = v2.Y; + verts[1].x = verts[3].x = v1.X; + verts[1].y = verts[3].y = v1.Y; + verts[0].z = brZ; + verts[1].z = blZ; + verts[2].z = trZ; + verts[3].z = tlZ; + + surf.SectorGroup = sectorGroup[back->Index(doomMap)]; + surf.Texture = side->GetTexture(WallPart::MIDDLE); + + AddWallVertices(surf, verts); + SetSideTextureUVs(surf, side, WallPart::TOP, tlZ, blZ, trZ, brZ); + AddSurfaceToTile(surf, doomMap, side->GetSampleDistance(WallPart::MIDDLE)); + + Surfaces.Push(surf); + } +} + +void DoomLevelMesh::CreateTopWallSurface(FLevel& doomMap, IntSideDef* side) +{ + IntSector* front = side->sectordef; + IntSector* back = (side->line->frontsector == front) ? side->line->backsector : side->line->frontsector; + + FVector2 v1 = side->V1(doomMap); + FVector2 v2 = side->V2(doomMap); + + float v1Top = (float)front->ceilingplane.ZatPoint(v1); + float v2Top = (float)front->ceilingplane.ZatPoint(v2); + float v1TopBack = (float)back->ceilingplane.ZatPoint(v1); + float v2TopBack = (float)back->ceilingplane.ZatPoint(v2); + + bool bSky = IsTopSideSky(front, back, side); + if (!bSky && !IsTopSideVisible(side)) + return; + + FFlatVertex verts[4]; + verts[0].x = verts[2].x = v1.X; + verts[0].y = verts[2].y = v1.Y; + verts[1].x = verts[3].x = v2.X; + verts[1].y = verts[3].y = v2.Y; + verts[0].z = v1TopBack; + verts[1].z = v2TopBack; + verts[2].z = v1Top; + verts[3].z = v2Top; + + DoomLevelMeshSurface surf; + surf.Side = side; + surf.Type = ST_UPPERSIDE; + surf.TypeIndex = side->Index(doomMap); + surf.IsSky = bSky; + surf.ControlSector = nullptr; + surf.SectorGroup = sectorGroup[front->Index(doomMap)]; + surf.Texture = side->GetTexture(WallPart::TOP); + + AddWallVertices(surf, verts); + SetSideTextureUVs(surf, side, WallPart::TOP, v1Top, v1TopBack, v2Top, v2TopBack); + AddSurfaceToTile(surf, doomMap, side->GetSampleDistance(WallPart::TOP)); + + Surfaces.Push(surf); +} + +void DoomLevelMesh::CreateBottomWallSurface(FLevel& doomMap, IntSideDef* side) +{ + if (!IsBottomSideVisible(side)) + return; + + IntSector* front = side->sectordef; + IntSector* back = (side->line->frontsector == front) ? side->line->backsector : side->line->frontsector; + + FVector2 v1 = side->V1(doomMap); + FVector2 v2 = side->V2(doomMap); + + float v1Bottom = (float)front->floorplane.ZatPoint(v1); + float v2Bottom = (float)front->floorplane.ZatPoint(v2); + float v1BottomBack = (float)back->floorplane.ZatPoint(v1); + float v2BottomBack = (float)back->floorplane.ZatPoint(v2); + + FFlatVertex verts[4]; + verts[0].x = verts[2].x = v1.X; + verts[0].y = verts[2].y = v1.Y; + verts[1].x = verts[3].x = v2.X; + verts[1].y = verts[3].y = v2.Y; + verts[0].z = v1Bottom; + verts[1].z = v2Bottom; + verts[2].z = v1BottomBack; + verts[3].z = v2BottomBack; + + DoomLevelMeshSurface surf; + surf.Side = side; + surf.Type = ST_LOWERSIDE; + surf.TypeIndex = side->Index(doomMap); + surf.IsSky = false; + surf.ControlSector = nullptr; + surf.SectorGroup = sectorGroup[front->Index(doomMap)]; + surf.Texture = side->GetTexture(WallPart::BOTTOM); + + AddWallVertices(surf, verts); + SetSideTextureUVs(surf, side, WallPart::BOTTOM, v1BottomBack, v1Bottom, v2BottomBack, v2Bottom); + AddSurfaceToTile(surf, doomMap, side->GetSampleDistance(WallPart::BOTTOM)); + + Surfaces.Push(surf); +} + +void DoomLevelMesh::SetSideTextureUVs(DoomLevelMeshSurface& surface, IntSideDef* side, WallPart texpart, float v1TopZ, float v1BottomZ, float v2TopZ, float v2BottomZ) +{ + FFlatVertex* verts = &Mesh.Vertices[surface.MeshLocation.StartVertIndex]; + +#if 0 + if (surface.Texture.isValid()) + { + const auto gtxt = TexMan.GetGameTexture(surface.Texture); + + FTexCoordInfo tci; + GetTexCoordInfo(gtxt, &tci, side, texpart); + + float startU = tci.FloatToTexU(tci.TextureOffset((float)side->GetTextureXOffset(texpart)) + tci.TextureOffset((float)side->GetTextureXOffset(texpart))); + float endU = startU + tci.FloatToTexU(side->TexelLength); + + verts[0].u = startU; + verts[1].u = endU; + verts[2].u = startU; + verts[3].u = endU; + + // To do: the ceiling version is apparently used in some situation related to 3d floors (rover->top.isceiling) + //float offset = tci.RowOffset((float)side->GetTextureYOffset(texpart)) + tci.RowOffset((float)side->GetTextureYOffset(texpart)) + (float)side->sector->GetPlaneTexZ(PLANE_CEILING); + float offset = tci.RowOffset((float)side->GetTextureYOffset(texpart)) + tci.RowOffset((float)side->GetTextureYOffset(texpart)) + (float)side->sector->GetPlaneTexZ(PLANE_FLOOR); + + verts[0].v = tci.FloatToTexV(offset - v1BottomZ); + verts[1].v = tci.FloatToTexV(offset - v2BottomZ); + verts[2].v = tci.FloatToTexV(offset - v1TopZ); + verts[3].v = tci.FloatToTexV(offset - v2TopZ); + } + else +#endif + { + for (int i = 0; i < 4; i++) + { + verts[i].u = 0.0f; + verts[i].v = 0.0f; + } + } +} + +void DoomLevelMesh::CreateFloorSurface(FLevel& doomMap, MapSubsectorEx* sub, IntSector* sector, IntSector* controlSector, int typeIndex) +{ + DoomLevelMeshSurface surf; + surf.Subsector = sub; + + Plane plane; + if (!controlSector) + { + plane = sector->floorplane; + surf.IsSky = IsSkySector(sector, PLANE_FLOOR); + } + else + { + plane = controlSector->ceilingplane; + plane.FlipVert(); + surf.IsSky = false; } - // Compress and store in lump - ZLibOut zout(wadFile); - wadFile.StartWritingLump("LIGHTMAP"); - zout.Write(buffer.data(), (int)(ptrdiff_t)(lumpFile.BufferAt() - lumpFile.Buffer())); + surf.MeshLocation.NumVerts = sub->numlines; + surf.MeshLocation.StartVertIndex = Mesh.Vertices.Size(); + surf.Texture = (controlSector ? controlSector : sector)->GetTexture(PLANE_FLOOR); + + FGameTexture* txt = TexMan.GetGameTexture(surf.Texture); + float w = txt->GetDisplayWidth(); + float h = txt->GetDisplayHeight(); + //VSMatrix mat = GetPlaneTextureRotationMatrix(txt, sector, PLANE_FLOOR); + VSMatrix mat; mat.loadIdentity(); + + Mesh.Vertices.Resize(surf.MeshLocation.StartVertIndex + surf.MeshLocation.NumVerts); + + FFlatVertex* verts = &Mesh.Vertices[surf.MeshLocation.StartVertIndex]; + + for (int j = 0; j < surf.MeshLocation.NumVerts; j++) + { + MapSegGLEx* seg = &doomMap.GLSegs[sub->firstline + (surf.MeshLocation.NumVerts - 1) - j]; + auto v = doomMap.GetSegVertex(seg->v1); + FVector2 v1(v.x, v.y); + FVector2 uv = (mat * FVector4(v1.X / 64.f, -v1.Y / 64.f, 0.f, 1.f)).XY(); // The magic 64.f and negative Y is based on SetFlatVertex + + verts[j].x = v1.X; + verts[j].y = v1.Y; + verts[j].z = (float)plane.ZatPoint(v1.X, v1.Y); + verts[j].u = uv.X; + verts[j].v = uv.Y; + } + + unsigned int startVertIndex = surf.MeshLocation.StartVertIndex; + unsigned int numElements = 0; + surf.MeshLocation.StartElementIndex = Mesh.Indexes.Size(); + for (int j = 2; j < surf.MeshLocation.NumVerts; j++) + { + Mesh.Indexes.Push(startVertIndex); + Mesh.Indexes.Push(startVertIndex + j - 1); + Mesh.Indexes.Push(startVertIndex + j); + numElements += 3; + } + surf.MeshLocation.NumElements = numElements; + surf.Bounds = GetBoundsFromSurface(surf); + + surf.Type = ST_FLOOR; + surf.TypeIndex = typeIndex; + surf.ControlSector = controlSector; + surf.Plane = FVector4((float)plane.Normal().X, (float)plane.Normal().Y, (float)plane.Normal().Z, -(float)plane.d); + surf.SectorGroup = sectorGroup[sector->Index(doomMap)]; + AddSurfaceToTile(surf, doomMap, (controlSector ? controlSector : sector)->sampleDistanceFloor); + + Surfaces.Push(surf); +} + +void DoomLevelMesh::CreateCeilingSurface(FLevel& doomMap, MapSubsectorEx* sub, IntSector* sector, IntSector* controlSector, int typeIndex) +{ + DoomLevelMeshSurface surf; + surf.Subsector = sub; + + Plane plane; + if (!controlSector) + { + plane = sector->ceilingplane; + surf.IsSky = IsSkySector(sector, PLANE_CEILING); + } + else + { + plane = controlSector->floorplane; + plane.FlipVert(); + surf.IsSky = false; + } + + surf.MeshLocation.NumVerts = sub->numlines; + surf.MeshLocation.StartVertIndex = Mesh.Vertices.Size(); + surf.Texture = (controlSector ? controlSector : sector)->GetTexture(PLANE_CEILING); + + FGameTexture* txt = TexMan.GetGameTexture(surf.Texture); + float w = txt->GetDisplayWidth(); + float h = txt->GetDisplayHeight(); + //VSMatrix mat = GetPlaneTextureRotationMatrix(txt, sector, PLANE_CEILING); + VSMatrix mat; mat.loadIdentity(); + + Mesh.Vertices.Resize(surf.MeshLocation.StartVertIndex + surf.MeshLocation.NumVerts); + + FFlatVertex* verts = &Mesh.Vertices[surf.MeshLocation.StartVertIndex]; + + for (int j = 0; j < surf.MeshLocation.NumVerts; j++) + { + MapSegGLEx* seg = &doomMap.GLSegs[sub->firstline + j]; + auto v = doomMap.GetSegVertex(seg->v1); + FVector2 v1 = FVector2(v.x, v.y); + FVector2 uv = (mat * FVector4(v1.X / 64.f, -v1.Y / 64.f, 0.f, 1.f)).XY(); // The magic 64.f and negative Y is based on SetFlatVertex + + verts[j].x = v1.X; + verts[j].y = v1.Y; + verts[j].z = (float)plane.ZatPoint(v1.X, v1.Y); + verts[j].u = uv.X; + verts[j].v = uv.Y; + } + + unsigned int startVertIndex = surf.MeshLocation.StartVertIndex; + unsigned int numElements = 0; + surf.MeshLocation.StartElementIndex = Mesh.Indexes.Size(); + for (int j = 2; j < surf.MeshLocation.NumVerts; j++) + { + Mesh.Indexes.Push(startVertIndex + j); + Mesh.Indexes.Push(startVertIndex + j - 1); + Mesh.Indexes.Push(startVertIndex); + numElements += 3; + } + surf.MeshLocation.NumElements = numElements; + surf.Bounds = GetBoundsFromSurface(surf); + + surf.Type = ST_CEILING; + surf.TypeIndex = typeIndex; + surf.ControlSector = controlSector; + surf.Plane = FVector4((float)plane.Normal().X, (float)plane.Normal().Y, (float)plane.Normal().Z, -(float)plane.d); + surf.SectorGroup = sectorGroup[sector->Index(doomMap)]; + AddSurfaceToTile(surf, doomMap, (controlSector ? controlSector : sector)->sampleDistanceCeiling); + + Surfaces.Push(surf); +} + +bool DoomLevelMesh::IsTopSideSky(IntSector* frontsector, IntSector* backsector, IntSideDef* side) +{ + return IsSkySector(frontsector, PLANE_CEILING) && IsSkySector(backsector, PLANE_CEILING); +} + +bool DoomLevelMesh::IsTopSideVisible(IntSideDef* side) +{ + //auto tex = TexMan.GetGameTexture(side->GetTexture(WallPart::TOP), true); + //return tex && tex->isValid(); + return true; +} + +bool DoomLevelMesh::IsBottomSideVisible(IntSideDef* side) +{ + //auto tex = TexMan.GetGameTexture(side->GetTexture(WallPart::BOTTOM), true); + //return tex && tex->isValid(); + return true; +} + +bool DoomLevelMesh::IsSkySector(IntSector* sector, SecPlaneType plane) +{ + // plane is either PLANE_CEILING or PLANE_FLOOR + return plane == PLANE_CEILING ? sector->skyCeiling : sector->skyFloor; //return sector->GetTexture(plane) == skyflatnum; +} + +bool DoomLevelMesh::IsDegenerate(const FVector3& v0, const FVector3& v1, const FVector3& v2) +{ + // A degenerate triangle has a zero cross product for two of its sides. + float ax = v1.X - v0.X; + float ay = v1.Y - v0.Y; + float az = v1.Z - v0.Z; + float bx = v2.X - v0.X; + float by = v2.Y - v0.Y; + float bz = v2.Z - v0.Z; + float crossx = ay * bz - az * by; + float crossy = az * bx - ax * bz; + float crossz = ax * by - ay * bx; + float crosslengthsqr = crossx * crossx + crossy * crossy + crossz * crossz; + return crosslengthsqr <= 1.e-6f; +} + +void DoomLevelMesh::AddSurfaceToTile(DoomLevelMeshSurface& surf, FLevel& doomMap, uint16_t sampleDimension) +{ + if (surf.IsSky) + { + surf.LightmapTileIndex = -1; + return; + } + + LightmapTileBinding binding; + binding.Type = surf.Type; + binding.TypeIndex = surf.TypeIndex; + binding.ControlSector = surf.ControlSector ? surf.ControlSector->Index(doomMap) : (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); + + surf.LightmapTileIndex = index; + } + else + { + int index = LightmapTiles.Size(); + + LightmapTile tile; + tile.Binding = binding; + tile.Bounds = surf.Bounds; + tile.Plane = surf.Plane; + tile.SampleDimension = GetSampleDimension(surf, sampleDimension); + + LightmapTiles.Push(tile); + bindings[binding] = index; + + surf.LightmapTileIndex = index; + } +} + +int DoomLevelMesh::GetSampleDimension(const DoomLevelMeshSurface& surf, uint16_t sampleDimension) +{ + if (sampleDimension <= 0) + { + sampleDimension = LightmapSampleDistance; + } + + sampleDimension = uint16_t(std::max(int(roundf(float(sampleDimension) / std::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 DoomLevelMesh::SortIndexes() +{ + // Order surfaces by pipeline + std::unordered_map> pipelineSurfaces; + for (int i = 0; i < (int)Surfaces.Size(); i++) + { + DoomLevelMeshSurface* s = &Surfaces[i]; + pipelineSurfaces[(int64_t(s->PipelineID) << 32) | int64_t(s->IsSky)].Push(i); + } + + // Create reorder surface indexes by pipeline and create a draw range for each + TArray sortedIndexes; + for (const auto& it : pipelineSurfaces) + { + LevelSubmeshDrawRange range; + range.PipelineID = it.first >> 32; + range.Start = sortedIndexes.Size(); + + // Move indexes to new array + for (unsigned int i : it.second) + { + DoomLevelMeshSurface& s = Surfaces[i]; + + unsigned int start = s.MeshLocation.StartElementIndex; + unsigned int count = s.MeshLocation.NumElements; + + s.MeshLocation.StartElementIndex = sortedIndexes.Size(); + + for (unsigned int j = 0; j < count; j++) + { + sortedIndexes.Push(Mesh.Indexes[start + j]); + } + + for (unsigned int j = 0; j < count; j += 3) + { + Mesh.SurfaceIndexes.Push((int)i); + } + } + + range.Count = sortedIndexes.Size() - range.Start; + + if ((it.first & 1) == 0) + DrawList.Push(range); + else + PortalList.Push(range); + } + + Mesh.Indexes.Swap(sortedIndexes); +} + +BBox DoomLevelMesh::GetBoundsFromSurface(const LevelMeshSurface& surface) const +{ + BBox bounds; + bounds.Clear(); + for (int i = int(surface.MeshLocation.StartVertIndex); i < int(surface.MeshLocation.StartVertIndex) + surface.MeshLocation.NumVerts; i++) + { + FVector3 v = Mesh.Vertices[(int)i].fPos(); + bounds.min.X = std::min(bounds.min.X, v.X); + bounds.min.Y = std::min(bounds.min.Y, v.Y); + bounds.min.Z = std::min(bounds.min.Z, v.Z); + bounds.max.X = std::max(bounds.max.X, v.X); + bounds.max.Y = std::max(bounds.max.Y, v.Y); + bounds.max.Z = std::max(bounds.max.Z, v.Z); + } + return bounds; } void DoomLevelMesh::DumpMesh(const FString& objFilename, const FString& mtlFilename) const { - DoomLevelSubmesh* submesh = static_cast(StaticMesh.get()); - auto f = fopen(objFilename.GetChars(), "w"); fprintf(f, "# DoomLevelMesh debug export\n"); - fprintf(f, "# Vertices: %u, Indexes: %u, Surfaces: %u\n", submesh->Mesh.Vertices.Size(), submesh->Mesh.Indexes.Size(), submesh->Surfaces.Size()); + fprintf(f, "# Vertices: %u, Indexes: %u, Surfaces: %u\n", Mesh.Vertices.Size(), Mesh.Indexes.Size(), Surfaces.Size()); fprintf(f, "mtllib %s\n", mtlFilename.GetChars()); double scale = 1 / 10.0; - for (const auto& v : submesh->Mesh.Vertices) + for (const auto& v : Mesh.Vertices) { fprintf(f, "v %f %f %f\n", v.x * scale, v.y * scale, v.z * scale); } - for (const auto& v : submesh->Mesh.Vertices) + for (const auto& v : Mesh.Vertices) { fprintf(f, "vt %f %f\n", v.lu, v.lv); } @@ -256,15 +990,15 @@ void DoomLevelMesh::DumpMesh(const FString& objFilename, const FString& mtlFilen bool useErrorMaterial = false; int highestUsedAtlasPage = -1; - for (unsigned i = 0, count = submesh->Mesh.Indexes.Size(); i + 2 < count; i += 3) + for (unsigned i = 0, count = Mesh.Indexes.Size(); i + 2 < count; i += 3) { - auto index = submesh->Mesh.SurfaceIndexes[i / 3]; + auto index = Mesh.SurfaceIndexes[i / 3]; if (index != lastSurfaceIndex) { lastSurfaceIndex = index; - if (unsigned(index) >= submesh->Surfaces.Size()) + if (unsigned(index) >= Surfaces.Size()) { fprintf(f, "o Surface[%d] (bad index)\n", index); fprintf(f, "usemtl error\n"); @@ -273,12 +1007,12 @@ void DoomLevelMesh::DumpMesh(const FString& objFilename, const FString& mtlFilen } else { - const auto& surface = submesh->Surfaces[index]; + const auto& surface = Surfaces[index]; fprintf(f, "o Surface[%d] %s %d%s\n", index, name(surface.Type), surface.TypeIndex, surface.IsSky ? " sky" : ""); if (surface.LightmapTileIndex >= 0) { - auto& tile = submesh->LightmapTiles[surface.LightmapTileIndex]; + auto& tile = LightmapTiles[surface.LightmapTileIndex]; fprintf(f, "usemtl lightmap%d\n", tile.AtlasLocation.ArrayIndex); if (tile.AtlasLocation.ArrayIndex > highestUsedAtlasPage) @@ -291,9 +1025,9 @@ void DoomLevelMesh::DumpMesh(const FString& objFilename, const FString& mtlFilen // fprintf(f, "f %d %d %d\n", MeshElements[i] + 1, MeshElements[i + 1] + 1, MeshElements[i + 2] + 1); fprintf(f, "f %d/%d %d/%d %d/%d\n", - submesh->Mesh.Indexes[i + 0] + 1, submesh->Mesh.Indexes[i + 0] + 1, - submesh->Mesh.Indexes[i + 1] + 1, submesh->Mesh.Indexes[i + 1] + 1, - submesh->Mesh.Indexes[i + 2] + 1, submesh->Mesh.Indexes[i + 2] + 1); + Mesh.Indexes[i + 0] + 1, Mesh.Indexes[i + 0] + 1, + Mesh.Indexes[i + 1] + 1, Mesh.Indexes[i + 1] + 1, + Mesh.Indexes[i + 2] + 1, Mesh.Indexes[i + 2] + 1); } @@ -383,9 +1117,10 @@ void DoomLevelMesh::CreatePortals(FLevel& doomMap) auto d = sector->GetPortalDisplacement(plane); if (!d.isZero()) { + // Note: Y and Z is swapped in the shader due to how the hwrenderer was implemented VSMatrix transformation; transformation.loadIdentity(); - transformation.translate((float)d.X, (float)d.Y, 0.0f); + transformation.translate((float)d.X, 0.0f, (float)d.Y); int targetSectorGroup = 0; auto portalDestination = sector->GetPortal(plane)->mDestination; @@ -422,8 +1157,9 @@ void DoomLevelMesh::CreatePortals(FLevel& doomMap) for (unsigned int i = 0, count = doomMap.Lines.Size(); i < count; i++) { linePortals[i] = 0; + #ifdef NEEDS_PORTING - IntLineDef* sourceLine = &doomMap.Lines[i]; + IntLineDef* sourceLine = &doomMap.lines[i]; if (sourceLine->isLinePortal()) { VSMatrix transformation; @@ -454,8 +1190,9 @@ void DoomLevelMesh::CreatePortals(FLevel& doomMap) z = tz - sz; } - transformation.rotate((float)sourceLine->getPortalAngleDiff().Degrees(), 0.0f, 0.0f, 1.0f); - transformation.translate((float)(targetXYZ.X - sourceXYZ.X), (float)(targetXYZ.Y - sourceXYZ.Y), (float)z); + // Note: Y and Z is swapped in the shader due to how the hwrenderer was implemented + transformation.rotate((float)sourceLine->getPortalAngleDiff().Degrees(), 0.0f, 1.0f, 0.0f); + transformation.translate((float)(targetXYZ.X - sourceXYZ.X), (float)z, (float)(targetXYZ.Y - sourceXYZ.Y)); int targetSectorGroup = 0; if (auto sector = targetLine->frontsector ? targetLine->frontsector : targetLine->backsector) @@ -481,69 +1218,145 @@ void DoomLevelMesh::CreatePortals(FLevel& doomMap) } } -void DoomLevelMesh::BuildLightLists(FLevel& doomMap) +void DoomLevelMesh::AddLightmapLump(FLevel& doomMap, FWadWriter& wadFile) { - for (unsigned i = 0; i < doomMap.ThingLights.Size(); ++i) + /* + // LIGHTMAP V2 pseudo-C specification: + + struct LightmapLump { - printf(" Building light lists: %u / %u\r", i, doomMap.ThingLights.Size()); - PropagateLight(doomMap, &doomMap.ThingLights[i]); - } + int version = 2; + uint32_t tileCount; + uint32_t pixelCount; + uint32_t uvCount; + SurfaceEntry surfaces[surfaceCount]; + uint16_t pixels[pixelCount * 3]; + float uvs[uvCount * 2]; + }; - printf(" Building light lists: %u / %u\n", doomMap.ThingLights.Size(), doomMap.ThingLights.Size()); -} - -void DoomLevelMesh::PropagateLight(FLevel& doomMap, ThingLight* light, int recursiveDepth) -{ - if (recursiveDepth > 32) - return; - - auto submesh = static_cast(StaticMesh.get()); - - SphereShape sphere; - sphere.center = light->LightRelativeOrigin(); - sphere.radius = light->LightRadius(); - //std::set portalsToErase; - for (int triangleIndex : TriangleMeshShape::find_all_hits(submesh->Collision.get(), &sphere)) + struct TileEntry { - DoomLevelMeshSurface* surface = &submesh->Surfaces[submesh->Mesh.SurfaceIndexes[triangleIndex]]; + uint32_t type, typeIndex; + uint32_t controlSector; // 0xFFFFFFFF is none + uint16_t width, height; // in pixels + uint32_t pixelsOffset; // offset in pixels array + vec3 translateWorldToLocal; + vec3 projLocalToU; + vec3 projLocalToV; + }; + */ + // Calculate size of lump + uint32_t tileCount = 0; + uint32_t pixelCount = 0; - // skip any surface which isn't physically connected to the sector group in which the light resides - //if (light->sectorGroup == surface->sectorGroup) + for (unsigned int i = 0; i < LightmapTiles.Size(); i++) + { + LightmapTile* tile = &LightmapTiles[i]; + if (tile->AtlasLocation.ArrayIndex != -1) { - /*if (surface->portalIndex >= 0) - { - auto portal = portals[surface->portalIndex].get(); - - if (touchedPortals.insert(*portal).second) - { - auto fakeLight = std::make_unique(*light); - - fakeLight->relativePosition.emplace(portal->TransformPosition(light->LightRelativeOrigin())); - fakeLight->sectorGroup = portal->targetSectorGroup; - - PropagateLight(doomMap, fakeLight.get(), recursiveDepth + 1); - portalsToErase.insert(*portal); - portalLights.push_back(std::move(fakeLight)); - } - }*/ - - // Add light to the list if it isn't already there - bool found = false; - for (ThingLight* light2 : surface->LightList) - { - if (light2 == light) - { - found = true; - break; - } - } - if (!found) - surface->LightList.push_back(light); + tileCount++; + pixelCount += tile->AtlasLocation.Area(); } } - /*for (auto& portal : portalsToErase) + printf(" Writing %u tiles out of %llu\n", tileCount, (size_t)LightmapTiles.Size()); + + const int version = 3; + + const uint32_t headerSize = sizeof(int) + 2 * sizeof(uint32_t); + const uint32_t bytesPerTileEntry = sizeof(uint32_t) * 4 + sizeof(uint16_t) * 2 + sizeof(float) * 9; + const uint32_t bytesPerPixel = sizeof(uint16_t) * 3; // F16 RGB + + uint32_t lumpSize = headerSize + tileCount * bytesPerTileEntry + pixelCount * bytesPerPixel; + + bool debug = false; + + if (debug) { - touchedPortals.erase(portal); - }*/ -} \ No newline at end of file + printf("Lump size %u bytes\n", lumpSize); + printf("Tiles: %u\nPixels: %u\n", tileCount, pixelCount); + } + + // Setup buffer + std::vector buffer(lumpSize); + BinFile lumpFile; + lumpFile.SetBuffer(buffer.data()); + + // Write header + lumpFile.Write32(version); + lumpFile.Write32(tileCount); + lumpFile.Write32(pixelCount); + + if (debug) + { + printf("--- Saving tiles ---\n"); + } + + // Write tiles + uint32_t pixelsOffset = 0; + + for (unsigned int i = 0; i < LightmapTiles.Size(); i++) + { + LightmapTile* tile = &LightmapTiles[i]; + + if (tile->AtlasLocation.ArrayIndex == -1) + continue; + + lumpFile.Write32(tile->Binding.Type); + lumpFile.Write32(tile->Binding.TypeIndex); + lumpFile.Write32(tile->Binding.ControlSector); + + lumpFile.Write16(uint16_t(tile->AtlasLocation.Width)); + lumpFile.Write16(uint16_t(tile->AtlasLocation.Height)); + + lumpFile.Write32(pixelsOffset * 3); + + lumpFile.WriteFloat(tile->Transform.TranslateWorldToLocal.X); + lumpFile.WriteFloat(tile->Transform.TranslateWorldToLocal.Y); + lumpFile.WriteFloat(tile->Transform.TranslateWorldToLocal.Z); + + lumpFile.WriteFloat(tile->Transform.ProjLocalToU.X); + lumpFile.WriteFloat(tile->Transform.ProjLocalToU.Y); + lumpFile.WriteFloat(tile->Transform.ProjLocalToU.Z); + + lumpFile.WriteFloat(tile->Transform.ProjLocalToV.X); + lumpFile.WriteFloat(tile->Transform.ProjLocalToV.Y); + lumpFile.WriteFloat(tile->Transform.ProjLocalToV.Z); + + pixelsOffset += tile->AtlasLocation.Area(); + } + + if (debug) + { + printf("--- Saving pixels ---\n"); + } + + // Write surface pixels + for (unsigned int i = 0; i < LightmapTiles.Size(); i++) + { + LightmapTile* tile = &LightmapTiles[i]; + + if (tile->AtlasLocation.ArrayIndex == -1) + continue; + + const uint16_t* pixels = LMTextureData.Data() + tile->AtlasLocation.ArrayIndex * LMTextureSize * LMTextureSize * 4; + int width = tile->AtlasLocation.Width; + int height = tile->AtlasLocation.Height; + for (int y = 0; y < height; y++) + { + const uint16_t* srcline = pixels + (tile->AtlasLocation.X + (tile->AtlasLocation.Y + y) * LMTextureSize) * 4; + for (int x = 0; x < width; x++) + { + lumpFile.Write16(*(srcline++)); + lumpFile.Write16(*(srcline++)); + lumpFile.Write16(*(srcline++)); + srcline++; + } + } + } + + // Compress and store in lump + ZLibOut zout(wadFile); + wadFile.StartWritingLump("LIGHTMAP"); + zout.Write(buffer.data(), (int)(ptrdiff_t)(lumpFile.BufferAt() - lumpFile.Buffer())); +} diff --git a/src/lightmapper/doom_levelmesh.h b/src/lightmapper/doom_levelmesh.h index 19937ad..73baa6a 100644 --- a/src/lightmapper/doom_levelmesh.h +++ b/src/lightmapper/doom_levelmesh.h @@ -2,61 +2,20 @@ #include "framework/zstring.h" #include "hw_levelmesh.h" +#include "hw_lightmaptile.h" #include "level/doomdata.h" +#include struct FLevel; class FWadWriter; - -class DoomLevelMesh : public LevelMesh -{ -public: - DoomLevelMesh(FLevel& doomMap); - - int AddSurfaceLights(const LevelMeshSurface* surface, LevelMeshLight* list, int listMaxSize) override; - - void BeginFrame(FLevel& doomMap); - bool TraceSky(const FVector3& start, FVector3 direction, float dist); - void DumpMesh(const FString& objFilename, const FString& mtlFilename) const; - void AddLightmapLump(FLevel& doomMap, FWadWriter& out); - - void BuildSectorGroups(const FLevel& doomMap); - - TArray sectorGroup; // index is sector, value is sectorGroup - TArray sectorPortals[2]; // index is sector+plane, value is index into the portal list - TArray linePortals; // index is linedef, value is index into the portal list - -private: - void CreatePortals(FLevel& doomMap); - void BuildLightLists(FLevel& doomMap); - void PropagateLight(FLevel& doomMap, ThingLight* light, int recursiveDepth = 0); -}; - -#if 0 - -class DoomLevelMesh : public LevelMesh -{ -public: - DoomLevelMesh(FLevel& level, int samples, int lmdims); - - int AddSurfaceLights(const LevelMeshSurface* surface, LevelMeshLight* list, int listMaxSize) override; - void DumpMesh(const FString& objFilename, const FString& mtlFilename) const; - void AddLightmapLump(FLevel& doomMap, FWadWriter& out); - -private: - void BuildLightLists(FLevel& doomMap); - void PropagateLight(FLevel& doomMap, ThingLight* light, int recursiveDepth = 0); - - // Portal to portals[] index - //std::map portalCache; - - // Portal lights - //std::vector> portalLights; - //std::set touchedPortals; -}; +struct FPolyObj; +struct HWWallDispatcher; +class DoomLevelMesh; +class MeshBuilder; enum DoomLevelMeshSurfaceType { - ST_UNKNOWN, + ST_NONE, ST_MIDDLESIDE, ST_UPPERSIDE, ST_LOWERSIDE, @@ -66,62 +25,99 @@ enum DoomLevelMeshSurfaceType struct DoomLevelMeshSurface : public LevelMeshSurface { - DoomLevelMeshSurfaceType Type = ST_UNKNOWN; + DoomLevelMeshSurfaceType Type = ST_NONE; int TypeIndex = 0; MapSubsectorEx* Subsector = nullptr; IntSideDef* Side = nullptr; IntSector* ControlSector = nullptr; - float* TexCoords = nullptr; - - std::vector LightList; + int PipelineID = 0; }; -class DoomLevelSubmesh : public LevelSubmesh +struct SideSurfaceRange +{ + int StartSurface = 0; + int SurfaceCount = 0; +}; + +struct FlatSurfaceRange +{ + int StartSurface = 0; + int SurfaceCount = 0; +}; + +class DoomLevelMesh : public LevelMesh { public: - void CreateStatic(FLevel& doomMap); + DoomLevelMesh(FLevel& doomMap); LevelMeshSurface* GetSurface(int index) override { return &Surfaces[index]; } unsigned int GetSurfaceIndex(const LevelMeshSurface* surface) const override { return (unsigned int)(ptrdiff_t)(static_cast(surface) - Surfaces.Data()); } int GetSurfaceCount() override { return Surfaces.Size(); } + void BeginFrame(FLevel& doomMap); + bool TraceSky(const FVector3& start, FVector3 direction, float dist); void DumpMesh(const FString& objFilename, const FString& mtlFilename) const; - // Used by Maploader - void BindLightmapSurfacesToGeometry(FLevel& doomMap); - void PackLightmapAtlas(int lightmapStartIndex); - void CreatePortals(FLevel& doomMap); - - TArray Surfaces; - TArray LightmapUvs; - TArray sectorGroup; // index is sector, value is sectorGroup - -private: void BuildSectorGroups(const FLevel& doomMap); - void CreateSubsectorSurfaces(FLevel& doomMap); - void CreateCeilingSurface(FLevel& doomMap, MapSubsectorEx* sub, IntSector* sector, IntSector* controlSector, int typeIndex); - void CreateFloorSurface(FLevel& doomMap, MapSubsectorEx* sub, IntSector* sector, IntSector* controlSector, int typeIndex); + void AddLightmapLump(FLevel& doomMap, FWadWriter& wadFile); + + TArray Surfaces; + + TArray sectorGroup; // index is sector, value is sectorGroup + TArray sectorPortals[2]; // index is sector+plane, value is index into the portal list + TArray linePortals; // index is linedef, value is index into the portal list + + void CreateLights(FLevel& doomMap); + +private: + void CreateSurfaces(FLevel& doomMap); + void CreateSideSurfaces(FLevel& doomMap, IntSideDef* side); - void CreateLinePortalSurface(FLevel& doomMap, IntSideDef* side); void CreateLineHorizonSurface(FLevel& doomMap, IntSideDef* side); void CreateFrontWallSurface(FLevel& doomMap, IntSideDef* side); - void CreateTopWallSurface(FLevel& doomMap, IntSideDef* side); void CreateMidWallSurface(FLevel& doomMap, IntSideDef* side); - void CreateBottomWallSurface(FLevel& doomMap, IntSideDef* side); void Create3DFloorWallSurfaces(FLevel& doomMap, IntSideDef* side); + void CreateTopWallSurface(FLevel& doomMap, IntSideDef* side); + void CreateBottomWallSurface(FLevel& doomMap, IntSideDef* side); + void AddWallVertices(DoomLevelMeshSurface& surf, FFlatVertex* verts); void SetSideTextureUVs(DoomLevelMeshSurface& surface, IntSideDef* side, WallPart texpart, float v1TopZ, float v1BottomZ, float v2TopZ, float v2BottomZ); - void SetupLightmapUvs(FLevel& doomMap); + void CreateFloorSurface(FLevel& doomMap, MapSubsectorEx* sub, IntSector* sector, IntSector* controlSector, int typeIndex); + void CreateCeilingSurface(FLevel& doomMap, MapSubsectorEx* sub, IntSector* sector, IntSector* controlSector, int typeIndex); - void CreateIndexes(); + void AddSurfaceToTile(DoomLevelMeshSurface& surf, FLevel& doomMap, uint16_t sampleDimension); + int GetSampleDimension(const DoomLevelMeshSurface& surf, uint16_t sampleDimension); static bool IsTopSideSky(IntSector* frontsector, IntSector* backsector, IntSideDef* side); static bool IsTopSideVisible(IntSideDef* side); static bool IsBottomSideVisible(IntSideDef* side); static bool IsSkySector(IntSector* sector, SecPlaneType plane); + static bool IsDegenerate(const FVector3& v0, const FVector3& v1, const FVector3& v2); + + void SortIndexes(); + + BBox GetBoundsFromSurface(const LevelMeshSurface& surface) const; + + int AddSurfaceToTile(const DoomLevelMeshSurface& surf); + int GetSampleDimension(const DoomLevelMeshSurface& surf); + + void CreatePortals(FLevel& doomMap); + + void PropagateLight(FLevel& doomMap, ThingLight* light, int recursiveDepth); + int GetLightIndex(ThingLight* light, int portalgroup); + + static FVector4 ToPlane(const FFlatVertex& pt1, const FFlatVertex& pt2, const FFlatVertex& pt3) + { + return ToPlane(FVector3(pt1.x, pt1.y, pt1.z), FVector3(pt2.x, pt2.y, pt2.z), FVector3(pt3.x, pt3.y, pt3.z)); + } + + static FVector4 ToPlane(const FFlatVertex& pt1, const FFlatVertex& pt2, const FFlatVertex& pt3, const FFlatVertex& pt4) + { + return ToPlane(FVector3(pt1.x, pt1.y, pt1.z), FVector3(pt2.x, pt2.y, pt2.z), FVector3(pt3.x, pt3.y, pt3.z), FVector3(pt4.x, pt4.y, pt4.z)); + } static FVector4 ToPlane(const FVector3& pt1, const FVector3& pt2, const FVector3& pt3) { @@ -144,27 +140,7 @@ private: return ToPlane(pt1, pt2, pt3); } - // Lightmapper - - enum PlaneAxis - { - AXIS_YZ = 0, - AXIS_XZ, - AXIS_XY - }; - - static PlaneAxis BestAxis(const FVector4& p); - BBox GetBoundsFromSurface(const LevelMeshSurface& surface) const; - - inline int AllocUvs(int amount) { return LightmapUvs.Reserve(amount); } - - void BuildSurfaceParams(int lightMapTextureWidth, int lightMapTextureHeight, LevelMeshSurface& surface); - - static bool IsDegenerate(const FVector3& v0, const FVector3& v1, const FVector3& v2); - - static FVector2 ToFVector2(const DVector2& v) { return FVector2((float)v.X, (float)v.Y); } - static FVector3 ToFVector3(const DVector3& v) { return FVector3((float)v.X, (float)v.Y, (float)v.Z); } - static FVector4 ToFVector4(const DVector4& v) { return FVector4((float)v.X, (float)v.Y, (float)v.Z, (float)v.W); } + TArray Sides; + TArray Flats; + std::map bindings; }; - -#endif diff --git a/src/lightmapper/doom_levelsubmesh.cpp b/src/lightmapper/doom_levelsubmesh.cpp deleted file mode 100644 index 22f8f8b..0000000 --- a/src/lightmapper/doom_levelsubmesh.cpp +++ /dev/null @@ -1,1012 +0,0 @@ - -#include "doom_levelsubmesh.h" -#include "level/level.h" -#include "framework/halffloat.h" -#include - -extern float lm_scale; - -DoomLevelSubmesh::DoomLevelSubmesh(DoomLevelMesh* mesh, FLevel& doomMap, bool staticMesh) : LevelMesh(mesh), StaticMesh(staticMesh) -{ - LightmapSampleDistance = doomMap.DefaultSamples; - Reset(); - - if (StaticMesh) - { - CreateStaticSurfaces(doomMap); - - SortIndexes(); - BuildTileSurfaceLists(); - UpdateCollision(); - PackLightmapAtlas(doomMap, 0); - } -} - -void DoomLevelSubmesh::Update(FLevel& doomMap, int lightmapStartIndex) -{ - if (!StaticMesh) - { - Reset(); - - CreateDynamicSurfaces(doomMap); - - SortIndexes(); - BuildTileSurfaceLists(); - UpdateCollision(); - PackLightmapAtlas(doomMap, lightmapStartIndex); - } -} - -void DoomLevelSubmesh::Reset() -{ - Surfaces.Clear(); - Mesh.Vertices.Clear(); - Mesh.Indexes.Clear(); - Mesh.SurfaceIndexes.Clear(); - Mesh.UniformIndexes.Clear(); - Mesh.Uniforms.Clear(); - Mesh.Materials.Clear(); -} - -void DoomLevelSubmesh::CreateStaticSurfaces(FLevel& doomMap) -{ - std::map bindings; - - // Create surface objects for all sides - for (unsigned int i = 0; i < doomMap.Sides.Size(); i++) - { - CreateSideSurfaces(bindings, doomMap, &doomMap.Sides[i]); - } - - // Create surfaces for all flats - for (int i = 0; i < doomMap.NumGLSubsectors; i++) - { - MapSubsectorEx* sub = &doomMap.GLSubsectors[i]; - - if (sub->numlines < 3) - { - continue; - } - - IntSector* sector = sub->GetSector(doomMap); - if (!sector) - continue; - - CreateFloorSurface(bindings, doomMap, sub, sector, nullptr, i); - CreateCeilingSurface(bindings, doomMap, sub, sector, nullptr, i); - - for (unsigned int j = 0; j < sector->x3dfloors.Size(); j++) - { - CreateFloorSurface(bindings, doomMap, sub, sector, sector->x3dfloors[j], i); - CreateCeilingSurface(bindings, doomMap, sub, sector, sector->x3dfloors[j], i); - } - } - - for (auto& tile : LightmapTiles) - { - SetupTileTransform(LMTextureSize, LMTextureSize, tile); - } -} - -void DoomLevelSubmesh::CreateSideSurfaces(std::map& bindings, FLevel& doomMap, IntSideDef* side) -{ - IntSector* front = side->sectordef; - IntSector* back = (side->line->frontsector == front) ? side->line->backsector : side->line->frontsector; - - FVector2 v1 = side->V1(doomMap); - FVector2 v2 = side->V2(doomMap); - - float v1Top = (float)front->ceilingplane.ZatPoint(v1); - float v1Bottom = (float)front->floorplane.ZatPoint(v1); - float v2Top = (float)front->ceilingplane.ZatPoint(v2); - float v2Bottom = (float)front->floorplane.ZatPoint(v2); - - /*if (side->line->getPortal() && side->line->frontsector == front) - { - CreateLinePortalSurface(bindings, doomMap, side); - } - else*/ if (side->line->special == Line_Horizon && front != back) - { - CreateLineHorizonSurface(bindings, doomMap, side); - } - else if (!back) - { - if (side->GetTexture(WallPart::MIDDLE).isValid()) - { - CreateFrontWallSurface(bindings, doomMap, side); - } - } - else - { - if (side->GetTexture(WallPart::MIDDLE).isValid()) - { - CreateMidWallSurface(bindings, doomMap, side); - } - - Create3DFloorWallSurfaces(bindings, doomMap, side); - - float v1TopBack = (float)back->ceilingplane.ZatPoint(v1); - float v1BottomBack = (float)back->floorplane.ZatPoint(v1); - float v2TopBack = (float)back->ceilingplane.ZatPoint(v2); - float v2BottomBack = (float)back->floorplane.ZatPoint(v2); - - if (v1Bottom < v1BottomBack || v2Bottom < v2BottomBack) - { - CreateBottomWallSurface(bindings, doomMap, side); - } - - if (v1Top > v1TopBack || v2Top > v2TopBack) - { - CreateTopWallSurface(bindings, doomMap, side); - } - } -} - -void DoomLevelSubmesh::AddWallVertices(DoomLevelMeshSurface& surf, FFlatVertex* verts) -{ - surf.MeshLocation.StartVertIndex = Mesh.Vertices.Size(); - surf.MeshLocation.StartElementIndex = Mesh.Indexes.Size(); - surf.MeshLocation.NumVerts = 4; - surf.MeshLocation.NumElements = 6; - surf.Plane = ToPlane(verts[2], verts[0], verts[3], verts[1]); - - Mesh.Vertices.Push(verts[0]); - Mesh.Vertices.Push(verts[1]); - Mesh.Vertices.Push(verts[2]); - Mesh.Vertices.Push(verts[3]); - - unsigned int startVertIndex = surf.MeshLocation.StartVertIndex; - Mesh.Indexes.Push(startVertIndex + 0); - Mesh.Indexes.Push(startVertIndex + 1); - Mesh.Indexes.Push(startVertIndex + 2); - Mesh.Indexes.Push(startVertIndex + 3); - Mesh.Indexes.Push(startVertIndex + 2); - Mesh.Indexes.Push(startVertIndex + 1); - - surf.Bounds = GetBoundsFromSurface(surf); -} - -void DoomLevelSubmesh::CreateLineHorizonSurface(std::map& bindings, FLevel& doomMap, IntSideDef* side) -{ - IntSector* front = side->sectordef; - - FVector2 v1 = side->V1(doomMap); - FVector2 v2 = side->V2(doomMap); - - float v1Top = (float)front->ceilingplane.ZatPoint(v1); - float v1Bottom = (float)front->floorplane.ZatPoint(v1); - float v2Top = (float)front->ceilingplane.ZatPoint(v2); - float v2Bottom = (float)front->floorplane.ZatPoint(v2); - - DoomLevelMeshSurface surf; - surf.Submesh = this; - surf.Type = ST_MIDDLESIDE; - surf.TypeIndex = side->Index(doomMap); - surf.Side = side; - surf.IsSky = front->skyFloor || front->skyCeiling; // front->GetTexture(PLANE_FLOOR) == skyflatnum || front->GetTexture(PLANE_CEILING) == skyflatnum; - surf.SectorGroup = LevelMesh->sectorGroup[front->Index(doomMap)]; - - FFlatVertex verts[4]; - verts[0].x = verts[2].x = v1.X; - verts[0].y = verts[2].y = v1.Y; - verts[1].x = verts[3].x = v2.X; - verts[1].y = verts[3].y = v2.Y; - verts[0].z = v1Bottom; - verts[1].z = v2Bottom; - verts[2].z = v1Top; - verts[3].z = v2Top; - AddWallVertices(surf, verts); - - SetSideTextureUVs(surf, side, WallPart::TOP, v1Top, v1Bottom, v2Top, v2Bottom); - - Surfaces.Push(surf); -} - -void DoomLevelSubmesh::CreateFrontWallSurface(std::map& bindings, FLevel& doomMap, IntSideDef* side) -{ - IntSector* front = side->sectordef; - - FVector2 v1 = side->V1(doomMap); - FVector2 v2 = side->V2(doomMap); - - float v1Top = (float)front->ceilingplane.ZatPoint(v1); - float v1Bottom = (float)front->floorplane.ZatPoint(v1); - float v2Top = (float)front->ceilingplane.ZatPoint(v2); - float v2Bottom = (float)front->floorplane.ZatPoint(v2); - - FFlatVertex verts[4]; - verts[0].x = verts[2].x = v1.X; - verts[0].y = verts[2].y = v1.Y; - verts[1].x = verts[3].x = v2.X; - verts[1].y = verts[3].y = v2.Y; - verts[0].z = v1Bottom; - verts[1].z = v2Bottom; - verts[2].z = v1Top; - verts[3].z = v2Top; - - DoomLevelMeshSurface surf; - surf.Submesh = this; - surf.Side = side; - surf.IsSky = false; - surf.Type = ST_MIDDLESIDE; - surf.TypeIndex = side->Index(doomMap); - surf.ControlSector = nullptr; - surf.SectorGroup = LevelMesh->sectorGroup[front->Index(doomMap)]; - surf.Texture = side->GetTexture(WallPart::MIDDLE); - AddWallVertices(surf, verts); - - SetSideTextureUVs(surf, side, WallPart::TOP, v1Top, v1Bottom, v2Top, v2Bottom); - AddSurfaceToTile(surf, bindings, doomMap, side->GetSampleDistance(WallPart::MIDDLE)); - - Surfaces.Push(surf); -} - -void DoomLevelSubmesh::CreateMidWallSurface(std::map& bindings, FLevel& doomMap, IntSideDef* side) -{ - IntSector* front = side->sectordef; - - FVector2 v1 = side->V1(doomMap); - FVector2 v2 = side->V2(doomMap); - - float v1Top = (float)front->ceilingplane.ZatPoint(v1); - float v1Bottom = (float)front->floorplane.ZatPoint(v1); - float v2Top = (float)front->ceilingplane.ZatPoint(v2); - float v2Bottom = (float)front->floorplane.ZatPoint(v2); - - FFlatVertex verts[4]; - verts[0].x = verts[2].x = v1.X; - verts[0].y = verts[2].y = v1.Y; - verts[1].x = verts[3].x = v2.X; - verts[1].y = verts[3].y = v2.Y; - - const auto& texture = side->GetTexture(WallPart::MIDDLE); - - //if ((side->Flags & WALLF_WRAP_MIDTEX) || (side->line->flags & WALLF_WRAP_MIDTEX)) - { - verts[0].z = v1Bottom; - verts[1].z = v2Bottom; - verts[2].z = v1Top; - verts[3].z = v2Top; - } - /*else - { - int offset = 0; - - auto gameTexture = TexMan.GetGameTexture(texture); - - float mid1Top = (float)(gameTexture->GetDisplayHeight() / side->GetTextureYScale(WallPart::MIDDLE)); - float mid2Top = (float)(gameTexture->GetDisplayHeight() / side->GetTextureYScale(WallPart::MIDDLE)); - float mid1Bottom = 0; - float mid2Bottom = 0; - - float yTextureOffset = (float)(side->GetTextureYOffset(WallPart::MIDDLE) / gameTexture->GetScaleY()); - - if (side->line->flags & ML_DONTPEGBOTTOM) - { - yTextureOffset += (float)side->sectordef->planes[PLANE_FLOOR].TexZ; - } - else - { - yTextureOffset += (float)(side->sectordef->planes[PLANE_CEILING].TexZ - gameTexture->GetDisplayHeight() / side->GetTextureYScale(WallPart::MIDDLE)); - } - - verts[0].z = std::min(std::max(yTextureOffset + mid1Bottom, v1Bottom), v1Top); - verts[1].z = std::min(std::max(yTextureOffset + mid2Bottom, v2Bottom), v2Top); - verts[2].z = std::max(std::min(yTextureOffset + mid1Top, v1Top), v1Bottom); - verts[3].z = std::max(std::min(yTextureOffset + mid2Top, v2Top), v2Bottom); - }*/ - - // mid texture - DoomLevelMeshSurface surf; - surf.Submesh = this; - surf.Side = side; - surf.IsSky = false; - - - surf.Type = ST_MIDDLESIDE; - surf.TypeIndex = side->Index(doomMap); - surf.ControlSector = nullptr; - surf.SectorGroup = LevelMesh->sectorGroup[front->Index(doomMap)]; - surf.Texture = texture; - // surf.alpha = float(side->line->alpha); - - // FVector3 offset = surf.Plane.XYZ() * 0.05f; // for better accuracy when raytracing mid-textures from each side - AddWallVertices(surf, verts/*, offset*/); - if (side->line->sidenum[0] != side->Index(doomMap)) - { - surf.Plane = -surf.Plane; - surf.Plane.W = -surf.Plane.W; - } - - SetSideTextureUVs(surf, side, WallPart::TOP, verts[2].z, verts[0].z, verts[3].z, verts[1].z); - AddSurfaceToTile(surf, bindings, doomMap, side->GetSampleDistance(WallPart::MIDDLE)); - - Surfaces.Push(surf); -} - -void DoomLevelSubmesh::Create3DFloorWallSurfaces(std::map& bindings, FLevel& doomMap, IntSideDef* side) -{ - IntSector* front = side->sectordef; - IntSector* back = (side->line->frontsector == front) ? side->line->backsector : side->line->frontsector; - - FVector2 v1 = side->V1(doomMap); - FVector2 v2 = side->V2(doomMap); - - float v1Top = (float)back->ceilingplane.ZatPoint(v1); - float v1Bottom = (float)back->floorplane.ZatPoint(v1); - float v2Top = (float)back->ceilingplane.ZatPoint(v2); - float v2Bottom = (float)back->floorplane.ZatPoint(v2); - - for (unsigned int j = 0; j < back->x3dfloors.Size(); j++) - { - IntSector* xfloor = back->x3dfloors[j]; - - // Don't create a line when both sectors have the same 3d floor - bool bothSides = false; - for (unsigned int k = 0; k < front->x3dfloors.Size(); k++) - { - if (front->x3dfloors[k] == xfloor) - { - bothSides = true; - break; - } - } - if (bothSides) - continue; - - DoomLevelMeshSurface surf; - surf.Submesh = this; - surf.Type = ST_MIDDLESIDE; - surf.TypeIndex = side->Index(doomMap); - surf.Side = side; - surf.ControlSector = xfloor; - surf.IsSky = false; - - float blZ = (float)xfloor->floorplane.ZatPoint(v1); - float brZ = (float)xfloor->floorplane.ZatPoint(v2); - float tlZ = (float)xfloor->ceilingplane.ZatPoint(v1); - float trZ = (float)xfloor->ceilingplane.ZatPoint(v2); - - FFlatVertex verts[4]; - verts[0].x = verts[2].x = v2.X; - verts[0].y = verts[2].y = v2.Y; - verts[1].x = verts[3].x = v1.X; - verts[1].y = verts[3].y = v1.Y; - verts[0].z = brZ; - verts[1].z = blZ; - verts[2].z = trZ; - verts[3].z = tlZ; - - surf.SectorGroup = LevelMesh->sectorGroup[back->Index(doomMap)]; - surf.Texture = side->GetTexture(WallPart::MIDDLE); - - AddWallVertices(surf, verts); - SetSideTextureUVs(surf, side, WallPart::TOP, tlZ, blZ, trZ, brZ); - AddSurfaceToTile(surf, bindings, doomMap, side->GetSampleDistance(WallPart::MIDDLE)); - - Surfaces.Push(surf); - } -} - -void DoomLevelSubmesh::CreateTopWallSurface(std::map& bindings, FLevel& doomMap, IntSideDef* side) -{ - IntSector* front = side->sectordef; - IntSector* back = (side->line->frontsector == front) ? side->line->backsector : side->line->frontsector; - - FVector2 v1 = side->V1(doomMap); - FVector2 v2 = side->V2(doomMap); - - float v1Top = (float)front->ceilingplane.ZatPoint(v1); - float v2Top = (float)front->ceilingplane.ZatPoint(v2); - float v1TopBack = (float)back->ceilingplane.ZatPoint(v1); - float v2TopBack = (float)back->ceilingplane.ZatPoint(v2); - - bool bSky = IsTopSideSky(front, back, side); - if (!bSky && !IsTopSideVisible(side)) - return; - - FFlatVertex verts[4]; - verts[0].x = verts[2].x = v1.X; - verts[0].y = verts[2].y = v1.Y; - verts[1].x = verts[3].x = v2.X; - verts[1].y = verts[3].y = v2.Y; - verts[0].z = v1TopBack; - verts[1].z = v2TopBack; - verts[2].z = v1Top; - verts[3].z = v2Top; - - DoomLevelMeshSurface surf; - surf.Submesh = this; - surf.Side = side; - surf.Type = ST_UPPERSIDE; - surf.TypeIndex = side->Index(doomMap); - surf.IsSky = bSky; - surf.ControlSector = nullptr; - surf.SectorGroup = LevelMesh->sectorGroup[front->Index(doomMap)]; - surf.Texture = side->GetTexture(WallPart::TOP); - - AddWallVertices(surf, verts); - SetSideTextureUVs(surf, side, WallPart::TOP, v1Top, v1TopBack, v2Top, v2TopBack); - AddSurfaceToTile(surf, bindings, doomMap, side->GetSampleDistance(WallPart::TOP)); - - Surfaces.Push(surf); -} - -void DoomLevelSubmesh::CreateBottomWallSurface(std::map& bindings, FLevel& doomMap, IntSideDef* side) -{ - if (!IsBottomSideVisible(side)) - return; - - IntSector* front = side->sectordef; - IntSector* back = (side->line->frontsector == front) ? side->line->backsector : side->line->frontsector; - - FVector2 v1 = side->V1(doomMap); - FVector2 v2 = side->V2(doomMap); - - float v1Bottom = (float)front->floorplane.ZatPoint(v1); - float v2Bottom = (float)front->floorplane.ZatPoint(v2); - float v1BottomBack = (float)back->floorplane.ZatPoint(v1); - float v2BottomBack = (float)back->floorplane.ZatPoint(v2); - - FFlatVertex verts[4]; - verts[0].x = verts[2].x = v1.X; - verts[0].y = verts[2].y = v1.Y; - verts[1].x = verts[3].x = v2.X; - verts[1].y = verts[3].y = v2.Y; - verts[0].z = v1Bottom; - verts[1].z = v2Bottom; - verts[2].z = v1BottomBack; - verts[3].z = v2BottomBack; - - DoomLevelMeshSurface surf; - surf.Submesh = this; - surf.Side = side; - surf.Type = ST_LOWERSIDE; - surf.TypeIndex = side->Index(doomMap); - surf.IsSky = false; - surf.ControlSector = nullptr; - surf.SectorGroup = LevelMesh->sectorGroup[front->Index(doomMap)]; - surf.Texture = side->GetTexture(WallPart::BOTTOM); - - AddWallVertices(surf, verts); - SetSideTextureUVs(surf, side, WallPart::BOTTOM, v1BottomBack, v1Bottom, v2BottomBack, v2Bottom); - AddSurfaceToTile(surf, bindings, doomMap, side->GetSampleDistance(WallPart::BOTTOM)); - - Surfaces.Push(surf); -} - -void DoomLevelSubmesh::SetSideTextureUVs(DoomLevelMeshSurface& surface, IntSideDef* side, WallPart texpart, float v1TopZ, float v1BottomZ, float v2TopZ, float v2BottomZ) -{ - FFlatVertex* verts = &Mesh.Vertices[surface.MeshLocation.StartVertIndex]; - -#if 0 - if (surface.Texture.isValid()) - { - const auto gtxt = TexMan.GetGameTexture(surface.Texture); - - FTexCoordInfo tci; - GetTexCoordInfo(gtxt, &tci, side, texpart); - - float startU = tci.FloatToTexU(tci.TextureOffset((float)side->GetTextureXOffset(texpart)) + tci.TextureOffset((float)side->GetTextureXOffset(texpart))); - float endU = startU + tci.FloatToTexU(side->TexelLength); - - verts[0].u = startU; - verts[1].u = endU; - verts[2].u = startU; - verts[3].u = endU; - - // To do: the ceiling version is apparently used in some situation related to 3d floors (rover->top.isceiling) - //float offset = tci.RowOffset((float)side->GetTextureYOffset(texpart)) + tci.RowOffset((float)side->GetTextureYOffset(texpart)) + (float)side->sector->GetPlaneTexZ(PLANE_CEILING); - float offset = tci.RowOffset((float)side->GetTextureYOffset(texpart)) + tci.RowOffset((float)side->GetTextureYOffset(texpart)) + (float)side->sector->GetPlaneTexZ(PLANE_FLOOR); - - verts[0].v = tci.FloatToTexV(offset - v1BottomZ); - verts[1].v = tci.FloatToTexV(offset - v2BottomZ); - verts[2].v = tci.FloatToTexV(offset - v1TopZ); - verts[3].v = tci.FloatToTexV(offset - v2TopZ); - } - else -#endif - { - for (int i = 0; i < 4; i++) - { - verts[i].u = 0.0f; - verts[i].v = 0.0f; - } - } -} - -void DoomLevelSubmesh::CreateFloorSurface(std::map& bindings, FLevel& doomMap, MapSubsectorEx* sub, IntSector* sector, IntSector* controlSector, int typeIndex) -{ - DoomLevelMeshSurface surf; - surf.Submesh = this; - surf.Subsector = sub; - - Plane plane; - if (!controlSector) - { - plane = sector->floorplane; - surf.IsSky = IsSkySector(sector, PLANE_FLOOR); - } - else - { - plane = controlSector->ceilingplane; - plane.FlipVert(); - surf.IsSky = false; - } - - surf.MeshLocation.NumVerts = sub->numlines; - surf.MeshLocation.StartVertIndex = Mesh.Vertices.Size(); - surf.Texture = (controlSector ? controlSector : sector)->GetTexture(PLANE_FLOOR); - - FGameTexture* txt = TexMan.GetGameTexture(surf.Texture); - float w = txt->GetDisplayWidth(); - float h = txt->GetDisplayHeight(); - //VSMatrix mat = GetPlaneTextureRotationMatrix(txt, sector, PLANE_FLOOR); - VSMatrix mat; mat.loadIdentity(); - - Mesh.Vertices.Resize(surf.MeshLocation.StartVertIndex + surf.MeshLocation.NumVerts); - - FFlatVertex* verts = &Mesh.Vertices[surf.MeshLocation.StartVertIndex]; - - for (int j = 0; j < surf.MeshLocation.NumVerts; j++) - { - MapSegGLEx* seg = &doomMap.GLSegs[sub->firstline + (surf.MeshLocation.NumVerts - 1) - j]; - auto v = doomMap.GetSegVertex(seg->v1); - FVector2 v1(v.x, v.y); - FVector2 uv = (mat * FVector4(v1.X / 64.f, -v1.Y / 64.f, 0.f, 1.f)).XY(); // The magic 64.f and negative Y is based on SetFlatVertex - - verts[j].x = v1.X; - verts[j].y = v1.Y; - verts[j].z = (float)plane.ZatPoint(v1.X, v1.Y); - verts[j].u = uv.X; - verts[j].v = uv.Y; - } - - unsigned int startVertIndex = surf.MeshLocation.StartVertIndex; - unsigned int numElements = 0; - surf.MeshLocation.StartElementIndex = Mesh.Indexes.Size(); - for (int j = 2; j < surf.MeshLocation.NumVerts; j++) - { - Mesh.Indexes.Push(startVertIndex); - Mesh.Indexes.Push(startVertIndex + j - 1); - Mesh.Indexes.Push(startVertIndex + j); - numElements += 3; - } - surf.MeshLocation.NumElements = numElements; - surf.Bounds = GetBoundsFromSurface(surf); - - surf.Type = ST_FLOOR; - surf.TypeIndex = typeIndex; - surf.ControlSector = controlSector; - surf.Plane = FVector4((float)plane.Normal().X, (float)plane.Normal().Y, (float)plane.Normal().Z, -(float)plane.d); - surf.SectorGroup = LevelMesh->sectorGroup[sector->Index(doomMap)]; - AddSurfaceToTile(surf, bindings, doomMap, (controlSector ? controlSector : sector)->sampleDistanceFloor); - - Surfaces.Push(surf); -} - -void DoomLevelSubmesh::CreateCeilingSurface(std::map& bindings, FLevel& doomMap, MapSubsectorEx* sub, IntSector* sector, IntSector* controlSector, int typeIndex) -{ - DoomLevelMeshSurface surf; - surf.Submesh = this; - surf.Subsector = sub; - - Plane plane; - if (!controlSector) - { - plane = sector->ceilingplane; - surf.IsSky = IsSkySector(sector, PLANE_CEILING); - } - else - { - plane = controlSector->floorplane; - plane.FlipVert(); - surf.IsSky = false; - } - - surf.MeshLocation.NumVerts = sub->numlines; - surf.MeshLocation.StartVertIndex = Mesh.Vertices.Size(); - surf.Texture = (controlSector ? controlSector : sector)->GetTexture(PLANE_CEILING); - - FGameTexture* txt = TexMan.GetGameTexture(surf.Texture); - float w = txt->GetDisplayWidth(); - float h = txt->GetDisplayHeight(); - //VSMatrix mat = GetPlaneTextureRotationMatrix(txt, sector, PLANE_CEILING); - VSMatrix mat; mat.loadIdentity(); - - Mesh.Vertices.Resize(surf.MeshLocation.StartVertIndex + surf.MeshLocation.NumVerts); - - FFlatVertex* verts = &Mesh.Vertices[surf.MeshLocation.StartVertIndex]; - - for (int j = 0; j < surf.MeshLocation.NumVerts; j++) - { - MapSegGLEx* seg = &doomMap.GLSegs[sub->firstline + j]; - auto v = doomMap.GetSegVertex(seg->v1); - FVector2 v1 = FVector2(v.x, v.y); - FVector2 uv = (mat * FVector4(v1.X / 64.f, -v1.Y / 64.f, 0.f, 1.f)).XY(); // The magic 64.f and negative Y is based on SetFlatVertex - - verts[j].x = v1.X; - verts[j].y = v1.Y; - verts[j].z = (float)plane.ZatPoint(v1.X, v1.Y); - verts[j].u = uv.X; - verts[j].v = uv.Y; - } - - unsigned int startVertIndex = surf.MeshLocation.StartVertIndex; - unsigned int numElements = 0; - surf.MeshLocation.StartElementIndex = Mesh.Indexes.Size(); - for (int j = 2; j < surf.MeshLocation.NumVerts; j++) - { - Mesh.Indexes.Push(startVertIndex + j); - Mesh.Indexes.Push(startVertIndex + j - 1); - Mesh.Indexes.Push(startVertIndex); - numElements += 3; - } - surf.MeshLocation.NumElements = numElements; - surf.Bounds = GetBoundsFromSurface(surf); - - surf.Type = ST_CEILING; - surf.TypeIndex = typeIndex; - surf.ControlSector = controlSector; - surf.Plane = FVector4((float)plane.Normal().X, (float)plane.Normal().Y, (float)plane.Normal().Z, -(float)plane.d); - surf.SectorGroup = LevelMesh->sectorGroup[sector->Index(doomMap)]; - AddSurfaceToTile(surf, bindings, doomMap, (controlSector ? controlSector : sector)->sampleDistanceCeiling); - - Surfaces.Push(surf); -} - -bool DoomLevelSubmesh::IsTopSideSky(IntSector* frontsector, IntSector* backsector, IntSideDef* side) -{ - return IsSkySector(frontsector, PLANE_CEILING) && IsSkySector(backsector, PLANE_CEILING); -} - -bool DoomLevelSubmesh::IsTopSideVisible(IntSideDef* side) -{ - //auto tex = TexMan.GetGameTexture(side->GetTexture(WallPart::TOP), true); - //return tex && tex->isValid(); - return true; -} - -bool DoomLevelSubmesh::IsBottomSideVisible(IntSideDef* side) -{ - //auto tex = TexMan.GetGameTexture(side->GetTexture(WallPart::BOTTOM), true); - //return tex && tex->isValid(); - return true; -} - -bool DoomLevelSubmesh::IsSkySector(IntSector* sector, SecPlaneType plane) -{ - // plane is either PLANE_CEILING or PLANE_FLOOR - return plane == PLANE_CEILING ? sector->skyCeiling : sector->skyFloor; //return sector->GetTexture(plane) == skyflatnum; -} - -bool DoomLevelSubmesh::IsDegenerate(const FVector3& v0, const FVector3& v1, const FVector3& v2) -{ - // A degenerate triangle has a zero cross product for two of its sides. - float ax = v1.X - v0.X; - float ay = v1.Y - v0.Y; - float az = v1.Z - v0.Z; - float bx = v2.X - v0.X; - float by = v2.Y - v0.Y; - float bz = v2.Z - v0.Z; - float crossx = ay * bz - az * by; - float crossy = az * bx - ax * bz; - float crossz = ax * by - ay * bx; - float crosslengthsqr = crossx * crossx + crossy * crossy + crossz * crossz; - return crosslengthsqr <= 1.e-6f; -} - -void DoomLevelSubmesh::AddSurfaceToTile(DoomLevelMeshSurface& surf, std::map& bindings, FLevel& doomMap, uint16_t sampleDimension) -{ - if (surf.IsSky) - { - surf.LightmapTileIndex = -1; - return; - } - - LightmapTileBinding binding; - binding.Type = surf.Type; - binding.TypeIndex = surf.TypeIndex; - binding.ControlSector = surf.ControlSector ? surf.ControlSector->Index(doomMap) : (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); - - surf.LightmapTileIndex = index; - } - else - { - int index = LightmapTiles.Size(); - - LightmapTile tile; - tile.Binding = binding; - tile.Bounds = surf.Bounds; - tile.Plane = surf.Plane; - tile.SampleDimension = GetSampleDimension(surf, sampleDimension); - - LightmapTiles.Push(tile); - bindings[binding] = index; - - surf.LightmapTileIndex = index; - } -} - -int DoomLevelSubmesh::GetSampleDimension(const DoomLevelMeshSurface& surf, uint16_t sampleDimension) -{ - if (sampleDimension <= 0) - { - sampleDimension = LightmapSampleDistance; - } - - sampleDimension = uint16_t(std::max(int(roundf(float(sampleDimension) / std::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::CreateDynamicSurfaces(FLevel& doomMap) -{ -} - -void DoomLevelSubmesh::SortIndexes() -{ - // Order surfaces by pipeline - std::unordered_map> pipelineSurfaces; - for (size_t i = 0; i < Surfaces.Size(); i++) - { - DoomLevelMeshSurface* s = &Surfaces[i]; - pipelineSurfaces[(int64_t(s->PipelineID) << 32) | int64_t(s->IsSky)].Push((int)i); - } - - // Create reorder surface indexes by pipeline and create a draw range for each - TArray sortedIndexes; - for (const auto& it : pipelineSurfaces) - { - LevelSubmeshDrawRange range; - range.PipelineID = it.first >> 32; - range.Start = sortedIndexes.Size(); - - // Move indexes to new array - for (unsigned int i : it.second) - { - DoomLevelMeshSurface& s = Surfaces[i]; - - unsigned int start = s.MeshLocation.StartElementIndex; - unsigned int count = s.MeshLocation.NumElements; - - s.MeshLocation.StartElementIndex = sortedIndexes.Size(); - - for (unsigned int j = 0; j < count; j++) - { - sortedIndexes.Push(Mesh.Indexes[start + j]); - } - - for (unsigned int j = 0; j < count; j += 3) - { - Mesh.SurfaceIndexes.Push((int)i); - } - } - - range.Count = sortedIndexes.Size() - range.Start; - - if ((it.first & 1) == 0) - DrawList.Push(range); - else - PortalList.Push(range); - } - - Mesh.Indexes.Swap(sortedIndexes); -} - -void DoomLevelSubmesh::PackLightmapAtlas(FLevel& doomMap, int lightmapStartIndex) -{ - std::vector sortedTiles; - sortedTiles.reserve(LightmapTiles.Size()); - - for (auto& tile : LightmapTiles) - { - sortedTiles.push_back(&tile); - } - - 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 (LightmapTile* tile : sortedTiles) - { - 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; - - 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++) - { - auto& vertex = Mesh.Vertices[surface.MeshLocation.StartVertIndex + i]; - FVector2 uv = tile.ToUV(vertex.fPos(), (float)LMTextureSize); - vertex.lu = uv.X; - vertex.lv = uv.Y; - vertex.lindex = (float)tile.AtlasLocation.ArrayIndex; - } - } - } - -#if 0 // Debug atlas tile locations: - float colors[30] = - { - 1.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, - 1.0f, 1.0f, 0.0f, - 0.0f, 1.0f, 1.0f, - 1.0f, 0.0f, 1.0f, - 0.5f, 0.0f, 0.0f, - 0.0f, 0.5f, 0.0f, - 0.5f, 0.5f, 0.0f, - 0.0f, 0.5f, 0.5f, - 0.5f, 0.0f, 0.5f - }; - LMTextureData.Resize(LMTextureSize * LMTextureSize * LMTextureCount * 4); - uint16_t* pixels = LMTextureData.Data(); - for (LightmapTile& tile : LightmapTiles) - { - tile.NeedsUpdate = false; - - int index = tile.Binding.TypeIndex; - float* color = colors + (index % 10) * 3; - - 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 + tile.AtlasLocation.ArrayIndex * LMTextureSize * LMTextureSize + yy * LMTextureSize * 4; - for (int xx = x; xx < x + w; xx++) - { - float gray = (yy - y) / (float)h; - line[xx * 4] = floatToHalf(color[0] * gray); - line[xx * 4 + 1] = floatToHalf(color[1] * gray); - line[xx * 4 + 2] = floatToHalf(color[2] * gray); - line[xx * 4 + 3] = 0x3c00; // half-float 1.0 - } - } - } - for (DoomLevelMeshSurface& surf : Surfaces) - { - surf.AlwaysUpdate = false; - } -#endif -} - -BBox DoomLevelSubmesh::GetBoundsFromSurface(const LevelMeshSurface& surface) const -{ - BBox bounds; - bounds.Clear(); - for (int i = int(surface.MeshLocation.StartVertIndex); i < int(surface.MeshLocation.StartVertIndex) + surface.MeshLocation.NumVerts; i++) - { - FVector3 v = Mesh.Vertices[(int)i].fPos(); - bounds.min.X = std::min(bounds.min.X, v.X); - bounds.min.Y = std::min(bounds.min.Y, v.Y); - bounds.min.Z = std::min(bounds.min.Z, v.Z); - bounds.max.X = std::max(bounds.max.X, v.X); - bounds.max.Y = std::max(bounds.max.Y, v.Y); - bounds.max.Z = std::max(bounds.max.Z, v.Z); - } - return bounds; -} - -DoomLevelSubmesh::PlaneAxis DoomLevelSubmesh::BestAxis(const FVector4& p) -{ - float na = fabs(float(p.X)); - float nb = fabs(float(p.Y)); - float nc = fabs(float(p.Z)); - - // figure out what axis the plane lies on - if (na >= nb && na >= nc) - { - return AXIS_YZ; - } - else if (nb >= na && nb >= nc) - { - return AXIS_XZ; - } - - return AXIS_XY; -} - -void DoomLevelSubmesh::SetupTileTransform(int lightMapTextureWidth, int lightMapTextureHeight, LightmapTile& tile) -{ - BBox bounds = tile.Bounds; - - // round off dimensions - FVector3 roundedSize; - for (int i = 0; i < 3; i++) - { - 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(tile.Plane); - - int width; - int height; - switch (axis) - { - default: - case AXIS_YZ: - width = (int)roundedSize.Y; - height = (int)roundedSize.Z; - 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 / 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 / tile.SampleDimension; - tCoords[1].Y = 1.0f / tile.SampleDimension; - break; - } - - // clamp width - if (width > lightMapTextureWidth - 2) - { - tCoords[0] *= ((float)(lightMapTextureWidth - 2) / (float)width); - width = (lightMapTextureWidth - 2); - } - - // clamp height - if (height > lightMapTextureHeight - 2) - { - tCoords[1] *= ((float)(lightMapTextureHeight - 2) / (float)height); - height = (lightMapTextureHeight - 2); - } - - tile.Transform.TranslateWorldToLocal = bounds.min; - tile.Transform.ProjLocalToU = tCoords[0]; - tile.Transform.ProjLocalToV = tCoords[1]; - - tile.AtlasLocation.Width = width; - tile.AtlasLocation.Height = height; -} diff --git a/src/lightmapper/doom_levelsubmesh.h b/src/lightmapper/doom_levelsubmesh.h deleted file mode 100644 index 15f3097..0000000 --- a/src/lightmapper/doom_levelsubmesh.h +++ /dev/null @@ -1,136 +0,0 @@ - -#pragma once - -#include "hw_levelmesh.h" -#include "framework/tarray.h" -#include "framework/vectors.h" -#include "framework/bounds.h" -#include "level/level.h" -#include -#include -#include - -typedef dp::rect_pack::RectPacker RectPacker; - -struct FLevel; -struct FPolyObj; -struct HWWallDispatcher; -class DoomLevelMesh; -class MeshBuilder; - -enum DoomLevelMeshSurfaceType -{ - ST_NONE, - ST_MIDDLESIDE, - ST_UPPERSIDE, - ST_LOWERSIDE, - ST_CEILING, - ST_FLOOR -}; - -struct DoomLevelMeshSurface : public LevelMeshSurface -{ - DoomLevelMeshSurfaceType Type = ST_NONE; - int TypeIndex = 0; - - MapSubsectorEx* Subsector = nullptr; - IntSideDef* Side = nullptr; - IntSector* ControlSector = nullptr; - - int PipelineID = 0; - - std::vector LightList; -}; - -class DoomLevelSubmesh : public LevelSubmesh -{ -public: - DoomLevelSubmesh(DoomLevelMesh* mesh, FLevel& doomMap, bool staticMesh); - - void Update(FLevel& doomMap, int lightmapStartIndex); - - LevelMeshSurface* GetSurface(int index) override { return &Surfaces[index]; } - unsigned int GetSurfaceIndex(const LevelMeshSurface* surface) const override { return (unsigned int)(ptrdiff_t)(static_cast(surface) - Surfaces.Data()); } - int GetSurfaceCount() override { return Surfaces.Size(); } - - TArray Surfaces; - -private: - void Reset(); - - void CreateStaticSurfaces(FLevel& doomMap); - void CreateDynamicSurfaces(FLevel& doomMap); - - void CreateSideSurfaces(std::map& bindings, FLevel& doomMap, IntSideDef* side); - void CreateLineHorizonSurface(std::map& bindings, FLevel& doomMap, IntSideDef* side); - void CreateFrontWallSurface(std::map& bindings, FLevel& doomMap, IntSideDef* side); - void CreateMidWallSurface(std::map& bindings, FLevel& doomMap, IntSideDef* side); - void Create3DFloorWallSurfaces(std::map& bindings, FLevel& doomMap, IntSideDef* side); - void CreateTopWallSurface(std::map& bindings, FLevel& doomMap, IntSideDef* side); - void CreateBottomWallSurface(std::map& bindings, FLevel& doomMap, IntSideDef* side); - void SetSideTextureUVs(DoomLevelMeshSurface& surface, IntSideDef* side, WallPart texpart, float v1TopZ, float v1BottomZ, float v2TopZ, float v2BottomZ); - void CreateFloorSurface(std::map& bindings, FLevel& doomMap, MapSubsectorEx* sub, IntSector* sector, IntSector* controlSector, int typeIndex); - void CreateCeilingSurface(std::map& bindings, FLevel& doomMap, MapSubsectorEx* sub, IntSector* sector, IntSector* controlSector, int typeIndex); - - void AddWallVertices(DoomLevelMeshSurface& surf, FFlatVertex* verts); - - static bool IsTopSideSky(IntSector* frontsector, IntSector* backsector, IntSideDef* side); - static bool IsTopSideVisible(IntSideDef* side); - static bool IsBottomSideVisible(IntSideDef* side); - static bool IsSkySector(IntSector* sector, SecPlaneType plane); - static bool IsDegenerate(const FVector3& v0, const FVector3& v1, const FVector3& v2); - - static FVector4 ToPlane(const FFlatVertex& pt1, const FFlatVertex& pt2, const FFlatVertex& pt3) - { - return ToPlane(FVector3(pt1.x, pt1.y, pt1.z), FVector3(pt2.x, pt2.y, pt2.z), FVector3(pt3.x, pt3.y, pt3.z)); - } - - static FVector4 ToPlane(const FFlatVertex& pt1, const FFlatVertex& pt2, const FFlatVertex& pt3, const FFlatVertex& pt4) - { - return ToPlane(FVector3(pt1.x, pt1.y, pt1.z), FVector3(pt2.x, pt2.y, pt2.z), FVector3(pt3.x, pt3.y, pt3.z), FVector3(pt4.x, pt4.y, pt4.z)); - } - - static FVector4 ToPlane(const FVector3& pt1, const FVector3& pt2, const FVector3& pt3) - { - FVector3 n = ((pt2 - pt1) ^ (pt3 - pt2)).Unit(); - float d = pt1 | n; - return FVector4(n.X, n.Y, n.Z, d); - } - - static FVector4 ToPlane(const FVector3& pt1, const FVector3& pt2, const FVector3& pt3, const FVector3& pt4) - { - if (pt1.ApproximatelyEquals(pt3)) - { - return ToPlane(pt1, pt2, pt4); - } - else if (pt1.ApproximatelyEquals(pt2) || pt2.ApproximatelyEquals(pt3)) - { - return ToPlane(pt1, pt3, pt4); - } - - return ToPlane(pt1, pt2, pt3); - } - - void SortIndexes(); - - void PackLightmapAtlas(FLevel& doomMap, int lightmapStartIndex); - - enum PlaneAxis - { - AXIS_YZ = 0, - AXIS_XZ, - AXIS_XY - }; - - static PlaneAxis BestAxis(const FVector4& p); - BBox GetBoundsFromSurface(const LevelMeshSurface& surface) const; - - void SetupTileTransform(int lightMapTextureWidth, int lightMapTextureHeight, LightmapTile& tile); - void AddSurfaceToTile(DoomLevelMeshSurface& surf, std::map& bindings, FLevel& doomMap, uint16_t sampleDimension); - int GetSampleDimension(const DoomLevelMeshSurface& surf, uint16_t sampleDimension); - - DoomLevelMesh* LevelMesh = nullptr; - bool StaticMesh = true; -}; - -static_assert(alignof(FVector2) == alignof(float[2]) && sizeof(FVector2) == sizeof(float) * 2); diff --git a/src/lightmapper/glsl/binding_lightmapper.glsl.h b/src/lightmapper/glsl/binding_lightmapper.glsl.h index 067c542..c24cc5a 100644 --- a/src/lightmapper/glsl/binding_lightmapper.glsl.h +++ b/src/lightmapper/glsl/binding_lightmapper.glsl.h @@ -15,7 +15,11 @@ struct SurfaceInfo uint PortalIndex; int TextureIndex; float Alpha; - float Padding; + float Padding0; + uint LightStart; + uint LightEnd; + uint Padding1; + uint Padding2; }; struct PortalInfo @@ -42,50 +46,27 @@ struct LightInfo layout(set = 0, binding = 1) buffer SurfaceIndexBuffer { uint surfaceIndices[]; }; layout(set = 0, binding = 2) buffer SurfaceBuffer { SurfaceInfo surfaces[]; }; layout(set = 0, binding = 3) buffer LightBuffer { LightInfo lights[]; }; -layout(set = 0, binding = 4) buffer PortalBuffer { PortalInfo portals[]; }; - -#if defined(USE_DRAWINDIRECT) +layout(set = 0, binding = 4) buffer LightIndexBuffer { int lightIndexes[]; }; +layout(set = 0, binding = 5) buffer PortalBuffer { PortalInfo portals[]; }; struct LightmapRaytracePC { - uint LightStart; - uint LightEnd; int SurfaceIndex; - int PushPadding1; + int Padding0; + int Padding1; + int Padding2; vec3 WorldToLocal; float TextureSize; vec3 ProjLocalToU; - float PushPadding2; + float Padding3; vec3 ProjLocalToV; - float PushPadding3; + float Padding4; float TileX; float TileY; float TileWidth; float TileHeight; }; -layout(std430, set = 0, binding = 5) buffer ConstantsBuffer { LightmapRaytracePC constants[]; }; - -#else - -layout(push_constant) uniform LightmapRaytracePC -{ - uint LightStart; - uint LightEnd; - int SurfaceIndex; - int PushPadding1; - vec3 WorldToLocal; - float TextureSize; - vec3 ProjLocalToU; - float PushPadding2; - vec3 ProjLocalToV; - float PushPadding3; - float TileX; - float TileY; - float TileWidth; - float TileHeight; -}; - -#endif +layout(std430, set = 0, binding = 6) buffer ConstantsBuffer { LightmapRaytracePC constants[]; }; )glsl"; diff --git a/src/lightmapper/glsl/frag_raytrace.glsl.h b/src/lightmapper/glsl/frag_raytrace.glsl.h index 93ea402..2cadc7c 100644 --- a/src/lightmapper/glsl/frag_raytrace.glsl.h +++ b/src/lightmapper/glsl/frag_raytrace.glsl.h @@ -8,38 +8,37 @@ static const char* frag_raytrace_glsl = R"glsl( #include #include #include - -#if defined(USE_DRAWINDIRECT) - -layout(location = 1) in flat int InstanceIndex; - -#endif +#include layout(location = 0) centroid in vec3 worldpos; +layout(location = 1) in flat int InstanceIndex; + layout(location = 0) out vec4 fragcolor; void main() { -#if defined(USE_DRAWINDIRECT) - uint LightStart = constants[InstanceIndex].LightStart; - uint LightEnd = constants[InstanceIndex].LightEnd; int SurfaceIndex = constants[InstanceIndex].SurfaceIndex; -#endif + uint LightStart = surfaces[SurfaceIndex].LightStart; + uint LightEnd = surfaces[SurfaceIndex].LightEnd; vec3 normal = surfaces[SurfaceIndex].Normal; - vec3 origin = worldpos + normal * 0.1; + vec3 origin = worldpos; #if defined(USE_SUNLIGHT) - vec3 incoming = TraceSunLight(origin, normal, SurfaceIndex); + vec3 incoming = TraceSunLight(origin, normal); #else vec3 incoming = vec3(0.0); #endif for (uint j = LightStart; j < LightEnd; j++) { - incoming += TraceLight(origin, normal, lights[j], SurfaceIndex); + incoming += TraceLight(origin, normal, lights[lightIndexes[j]], 0.0); } +#if defined(USE_BOUNCE) + incoming += TraceBounceLight(origin, normal); +#endif + #if defined(USE_AO) incoming.rgb *= TraceAmbientOcclusion(origin, normal); #endif diff --git a/src/lightmapper/glsl/montecarlo.glsl.h b/src/lightmapper/glsl/montecarlo.glsl.h new file mode 100644 index 0000000..39d23ea --- /dev/null +++ b/src/lightmapper/glsl/montecarlo.glsl.h @@ -0,0 +1,33 @@ +static const char* montecarlo_glsl = R"glsl( + +float RadicalInverse_VdC(uint bits) +{ + bits = (bits << 16u) | (bits >> 16u); + bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); + bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); + bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); + bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); + return float(bits) * 2.3283064365386963e-10f; // / 0x100000000 +} + +vec2 Hammersley(uint i, uint N) +{ + return vec2(float(i) / float(N), RadicalInverse_VdC(i)); +} + +vec2 getVogelDiskSample(int sampleIndex, int sampleCount, float phi) +{ + const float goldenAngle = radians(180.0) * (3.0 - sqrt(5.0)); + float sampleIndexF = float(sampleIndex); + float sampleCountF = float(sampleCount); + + float r = sqrt((sampleIndexF + 0.5) / sampleCountF); // Assuming index and count are positive + float theta = sampleIndexF * goldenAngle + phi; + + float sine = sin(theta); + float cosine = cos(theta); + + return vec2(cosine, sine) * r; +} + +)glsl"; diff --git a/src/lightmapper/glsl/polyfill_rayquery.glsl.h b/src/lightmapper/glsl/polyfill_rayquery.glsl.h index 2ca10b4..7f7beda 100644 --- a/src/lightmapper/glsl/polyfill_rayquery.glsl.h +++ b/src/lightmapper/glsl/polyfill_rayquery.glsl.h @@ -1,9 +1,18 @@ static const char* polyfill_rayquery_glsl = R"glsl( +struct TraceResult +{ + float t; + vec3 primitiveWeights; + int primitiveIndex; +}; + #if defined(USE_RAYQUERY) -int TraceFirstHitTriangleNoPortal(vec3 origin, float tmin, vec3 dir, float tmax, out float t, out vec3 primitiveWeights) +TraceResult TraceFirstHit(vec3 origin, float tmin, vec3 dir, float tmax) { + TraceResult result; + rayQueryEXT rayQuery; rayQueryInitializeEXT(rayQuery, acc, gl_RayFlagsCullBackFacingTrianglesEXT, 0xFF, origin, tmin, dir, tmax); @@ -17,18 +26,20 @@ int TraceFirstHitTriangleNoPortal(vec3 origin, float tmin, vec3 dir, float tmax, if (rayQueryGetIntersectionTypeEXT(rayQuery, true) == gl_RayQueryCommittedIntersectionTriangleEXT) { - t = rayQueryGetIntersectionTEXT(rayQuery, true); + result.t = rayQueryGetIntersectionTEXT(rayQuery, true); - primitiveWeights.xy = rayQueryGetIntersectionBarycentricsEXT(rayQuery, true); - primitiveWeights.z = 1.0 - primitiveWeights.x - primitiveWeights.y; + result.primitiveWeights.xy = rayQueryGetIntersectionBarycentricsEXT(rayQuery, true); + result.primitiveWeights.z = 1.0 - result.primitiveWeights.x - result.primitiveWeights.y; - return rayQueryGetIntersectionPrimitiveIndexEXT(rayQuery, true); + result.primitiveIndex = rayQueryGetIntersectionPrimitiveIndexEXT(rayQuery, true); } else { - t = tmax; - return -1; + result.t = tmax; + result.primitiveIndex = -1; } + + return result; } /* @@ -235,8 +246,10 @@ TraceHit find_first_hit(RayBBox ray) return hit; } -int TraceFirstHitTriangleNoPortal(vec3 origin, float tmin, vec3 dir, float tmax, out float tparam, out vec3 primitiveWeights) +TraceResult TraceFirstHit(vec3 origin, float tmin, vec3 dir, float tmax) { + TraceResult result; + // Perform segmented tracing to keep the ray AABB box smaller vec3 ray_start = origin; vec3 ray_end = origin + dir * tmax; @@ -252,16 +265,18 @@ int TraceFirstHitTriangleNoPortal(vec3 origin, float tmin, vec3 dir, float tmax, TraceHit hit = find_first_hit(ray); if (hit.fraction < 1.0) { - tparam = hit.fraction = segstart * (1.0 - hit.fraction) + segend * hit.fraction; - primitiveWeights.x = hit.b; - primitiveWeights.y = hit.c; - primitiveWeights.z = 1.0 - hit.b - hit.c; - return hit.triangle; + result.t = mix(segstart, segend, hit.fraction); + result.primitiveWeights.x = hit.b; + result.primitiveWeights.y = hit.c; + result.primitiveWeights.z = 1.0 - hit.b - hit.c; + result.primitiveIndex = hit.triangle; + return result; } } - tparam = tracedist; - return -1; + result.t = tracedist; + result.primitiveIndex = -1; + return result; } #endif diff --git a/src/lightmapper/glsl/trace_ambient_occlusion.glsl.h b/src/lightmapper/glsl/trace_ambient_occlusion.glsl.h index 760b844..5f6d5da 100644 --- a/src/lightmapper/glsl/trace_ambient_occlusion.glsl.h +++ b/src/lightmapper/glsl/trace_ambient_occlusion.glsl.h @@ -1,11 +1,12 @@ static const char* trace_ambient_occlusion_glsl = R"glsl( -vec2 Hammersley(uint i, uint N); -float RadicalInverse_VdC(uint bits); +#include + +float TraceAORay(vec3 origin, float tmin, vec3 dir, float tmax); float TraceAmbientOcclusion(vec3 origin, vec3 normal) { - const float minDistance = 0.05; + const float minDistance = 0.01; const float aoDistance = 100; const int SampleCount = 128; @@ -20,38 +21,42 @@ float TraceAmbientOcclusion(vec3 origin, vec3 normal) vec2 Xi = Hammersley(i, SampleCount); vec3 H = normalize(vec3(Xi.x * 2.0f - 1.0f, Xi.y * 2.0f - 1.0f, 1.5 - length(Xi))); vec3 L = H.x * tangent + H.y * bitangent + H.z * N; - - float hitDistance; - int primitiveID = TraceFirstHitTriangleT(origin, minDistance, L, aoDistance, hitDistance); - if (primitiveID != -1) - { - SurfaceInfo surface = surfaces[surfaceIndices[primitiveID]]; - if (surface.Sky == 0.0) - { - ambience += clamp(hitDistance / aoDistance, 0.0, 1.0); - } - } - else - { - ambience += 1.0; - } + ambience += clamp(TraceAORay(origin, minDistance, L, aoDistance) / aoDistance, 0.0, 1.0); } return ambience / float(SampleCount); } -vec2 Hammersley(uint i, uint N) +float TraceAORay(vec3 origin, float tmin, vec3 dir, float tmax) { - return vec2(float(i) / float(N), RadicalInverse_VdC(i)); -} + float tcur = 0.0; + for (int i = 0; i < 3; i++) + { + TraceResult result = TraceFirstHit(origin, tmin, dir, tmax - tcur); + if (result.primitiveIndex == -1) + return tmax; -float RadicalInverse_VdC(uint bits) -{ - bits = (bits << 16u) | (bits >> 16u); - bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); - bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); - bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); - bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); - return float(bits) * 2.3283064365386963e-10f; // / 0x100000000 + SurfaceInfo surface = GetSurface(result.primitiveIndex); + + // Stop if hit sky portal + if (surface.Sky > 0.0) + return tmax; + + // Stop if opaque surface + if (surface.PortalIndex == 0 /*surface.TextureIndex == 0*/) + { + return tcur + result.t; + } + + // Move to surface hit point + origin += dir * result.t; + tcur += result.t; + if (tcur >= tmax) + return tmax; + + // Move through the portal, if any + TransformRay(surface.PortalIndex, origin, dir); + } + return tmax; } )glsl"; diff --git a/src/lightmapper/glsl/trace_bounce.glsl.h b/src/lightmapper/glsl/trace_bounce.glsl.h new file mode 100644 index 0000000..8c39e7b --- /dev/null +++ b/src/lightmapper/glsl/trace_bounce.glsl.h @@ -0,0 +1,48 @@ +static const char* trace_bounce_glsl = R"glsl( + +#include + +vec3 TraceBounceLight(vec3 origin, vec3 normal) +{ + const float minDistance = 0.01; + const float maxDistance = 1000.0; + const int SampleCount = 8; + + vec3 N = normal; + vec3 up = abs(N.x) < abs(N.y) ? vec3(1.0, 0.0, 0.0) : vec3(0.0, 1.0, 0.0); + vec3 tangent = normalize(cross(up, N)); + vec3 bitangent = cross(N, tangent); + vec3 incoming = vec3(0.0); + + for (uint i = 0; i < SampleCount; i++) + { + vec2 Xi = Hammersley(i, SampleCount); + vec3 H = normalize(vec3(Xi.x * 2.0f - 1.0f, Xi.y * 2.0f - 1.0f, 1.5 - length(Xi))); + vec3 L = H.x * tangent + H.y * bitangent + H.z * N; + + TraceResult result = TraceFirstHit(origin, minDistance, L, maxDistance); + + // We hit nothing. + if (result.primitiveIndex == -1) + continue; + + SurfaceInfo surface = GetSurface(result.primitiveIndex); + uint LightStart = surface.LightStart; + uint LightEnd = surface.LightEnd; + vec3 surfacepos = origin + L * result.t; + + float angleAttenuation = max(dot(normal, L), 0.0); + +#if defined(USE_SUNLIGHT) + incoming += TraceSunLight(surfacepos, surface.Normal) * angleAttenuation; +#endif + + for (uint j = LightStart; j < LightEnd; j++) + { + incoming += TraceLight(surfacepos, surface.Normal, lights[lightIndexes[j]], result.t) * angleAttenuation; + } + } + return incoming / float(SampleCount); +} + +)glsl"; diff --git a/src/lightmapper/glsl/trace_levelmesh.glsl.h b/src/lightmapper/glsl/trace_levelmesh.glsl.h index 76244a6..0a830cd 100644 --- a/src/lightmapper/glsl/trace_levelmesh.glsl.h +++ b/src/lightmapper/glsl/trace_levelmesh.glsl.h @@ -1,127 +1,39 @@ static const char* trace_levelmesh_glsl = R"glsl( -vec4 rayColor; - -vec4 alphaBlend(vec4 a, vec4 b); vec4 BeerLambertSimple(vec4 medium, vec4 ray_color); -vec4 blend(vec4 a, vec4 b); -int TraceFirstHitTriangleT(vec3 origin, float tmin, vec3 dir, float tmax, out float t) +SurfaceInfo GetSurface(int primitiveIndex) { - int primitiveID = -1; - vec3 primitiveWeights; - for (int i = 0; i < 4; i++) + return surfaces[surfaceIndices[primitiveIndex]]; +} + +vec2 GetSurfaceUV(int primitiveIndex, vec3 primitiveWeights) +{ + int index = primitiveIndex * 3; + return + vertices[elements[index + 1]].uv * primitiveWeights.x + + vertices[elements[index + 2]].uv * primitiveWeights.y + + vertices[elements[index + 0]].uv * primitiveWeights.z; +} + +vec4 BlendTexture(SurfaceInfo surface, vec2 uv, vec4 rayColor) +{ + if (surface.TextureIndex == 0) { - primitiveID = TraceFirstHitTriangleNoPortal(origin, tmin, dir, tmax, t, primitiveWeights); - - if(primitiveID < 0) - { - break; - } - - SurfaceInfo surface = surfaces[surfaceIndices[primitiveID]]; - - if(surface.PortalIndex == 0) - { - int index = primitiveID * 3; - vec2 uv = vertices[elements[index + 1]].uv * primitiveWeights.x + vertices[elements[index + 2]].uv * primitiveWeights.y + vertices[elements[index + 0]].uv * primitiveWeights.z; - - if (surface.TextureIndex == 0) - { - break; - } - - vec4 color = texture(textures[surface.TextureIndex], uv); - color.w *= surface.Alpha; - - if (color.w > 0.999 || all(lessThan(rayColor.rgb, vec3(0.001)))) - { - break; - } - - rayColor = blend(color, rayColor); - } - - // Portal was hit: Apply transformation onto the ray - mat4 transformationMatrix = portals[surface.PortalIndex].Transformation; - - origin = (transformationMatrix * vec4(origin + dir * t, 1.0)).xyz; - dir = (transformationMatrix * vec4(dir, 0.0)).xyz; - tmax -= t; + return rayColor; } - return primitiveID; -} - -int TraceFirstHitTriangle(vec3 origin, float tmin, vec3 dir, float tmax) -{ - float t; - return TraceFirstHitTriangleT(origin, tmin, dir, tmax, t); -} - -bool TraceAnyHit(vec3 origin, float tmin, vec3 dir, float tmax) -{ - return TraceFirstHitTriangle(origin, tmin, dir, tmax) >= 0; -} - -bool TracePoint(vec3 origin, vec3 target, float tmin, vec3 dir, float tmax) -{ - int primitiveID; - float t; - vec3 primitiveWeights; - for (int i = 0; i < 4; i++) + else { - t = tmax; - primitiveID = TraceFirstHitTriangleNoPortal(origin, tmin, dir, tmax, t, primitiveWeights); - - origin += dir * t; - tmax -= t; - - if(primitiveID < 0) - { - // We didn't hit anything - break; - } - - SurfaceInfo surface = surfaces[surfaceIndices[primitiveID]]; - - if (surface.PortalIndex == 0) - { - int index = primitiveID * 3; - vec2 uv = vertices[elements[index + 1]].uv * primitiveWeights.x + vertices[elements[index + 2]].uv * primitiveWeights.y + vertices[elements[index + 0]].uv * primitiveWeights.z; - - if (surface.TextureIndex == 0) - { - break; - } - - vec4 color = texture(textures[surface.TextureIndex], uv); - color.w *= surface.Alpha; - - if (color.w > 0.999 || all(lessThan(rayColor.rgb, vec3(0.001)))) - { - break; - } - - rayColor = blend(color, rayColor); - } - - if(dot(surface.Normal, dir) >= 0.0) - { - continue; - } - - mat4 transformationMatrix = portals[surface.PortalIndex].Transformation; - origin = (transformationMatrix * vec4(origin, 1.0)).xyz; - dir = (transformationMatrix * vec4(dir, 0.0)).xyz; + vec4 color = texture(textures[surface.TextureIndex], uv); + return BeerLambertSimple(vec4(1.0 - color.rgb, color.a * surface.Alpha), rayColor); } - - return distance(origin, target) <= 1.0; } -vec4 alphaBlend(vec4 a, vec4 b) +void TransformRay(uint portalIndex, inout vec3 origin, inout vec3 dir) { - float na = a.w + b.w * (1.0 - a.w); - return vec4((a.xyz * a.w + b.xyz * b.w * (1.0 - a.w)) / na, max(0.001, na)); + mat4 transformationMatrix = portals[portalIndex].Transformation; + origin = (transformationMatrix * vec4(origin, 1.0)).xyz; + dir = (transformationMatrix * vec4(dir, 0.0)).xyz; } vec4 BeerLambertSimple(vec4 medium, vec4 ray_color) // based on Beer-Lambert law @@ -131,9 +43,4 @@ vec4 BeerLambertSimple(vec4 medium, vec4 ray_color) // based on Beer-Lambert law return ray_color; } -vec4 blend(vec4 a, vec4 b) -{ - return BeerLambertSimple(vec4(1.0 - a.rgb, a.w), b); -} - )glsl"; diff --git a/src/lightmapper/glsl/trace_light.glsl.h b/src/lightmapper/glsl/trace_light.glsl.h index 77bd6a4..20800d6 100644 --- a/src/lightmapper/glsl/trace_light.glsl.h +++ b/src/lightmapper/glsl/trace_light.glsl.h @@ -1,22 +1,20 @@ static const char* trace_light_glsl = R"glsl( -vec2 getVogelDiskSample(int sampleIndex, int sampleCount, float phi); +#include -vec3 TraceLight(vec3 origin, vec3 normal, LightInfo light, int surfaceIndex) +vec4 TracePointLightRay(vec3 origin, vec3 lightpos, float tmin, vec4 rayColor); + +vec3 TraceLight(vec3 origin, vec3 normal, LightInfo light, float extraDistance) { const float minDistance = 0.01; vec3 incoming = vec3(0.0); - float dist = distance(light.RelativeOrigin, origin); + float dist = distance(light.RelativeOrigin, origin) + extraDistance; if (dist > minDistance && dist < light.Radius) { vec3 dir = normalize(light.RelativeOrigin - origin); float distAttenuation = max(1.0 - (dist / light.Radius), 0.0); - float angleAttenuation = 1.0f; - if (surfaceIndex >= 0) - { - angleAttenuation = max(dot(normal, dir), 0.0); - } + float angleAttenuation = max(dot(normal, dir), 0.0); float spotAttenuation = 1.0; if (light.OuterAngleCos > -1.0) { @@ -28,6 +26,8 @@ vec3 TraceLight(vec3 origin, vec3 normal, LightInfo light, int surfaceIndex) float attenuation = distAttenuation * angleAttenuation * spotAttenuation; if (attenuation > 0.0) { + vec4 rayColor = vec4(light.Color.rgb * (attenuation * light.Intensity), 1.0); + #if defined(USE_SOFTSHADOWS) vec3 v = (abs(dir.x) > abs(dir.y)) ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0); @@ -41,19 +41,11 @@ vec3 TraceLight(vec3 origin, vec3 normal, LightInfo light, int surfaceIndex) vec2 gridoffset = getVogelDiskSample(i, step_count, gl_FragCoord.x + gl_FragCoord.y * 13.37) * lightsize; vec3 pos = light.Origin + xdir * gridoffset.x + ydir * gridoffset.y; - rayColor = vec4(light.Color.rgb, 1.0); - if (TracePoint(origin, pos, minDistance, normalize(pos - origin), distance(origin, pos))) - { - incoming.rgb += (rayColor.rgb * rayColor.w) * (attenuation * light.Intensity) / float(step_count); - } + incoming.rgb += TracePointLightRay(origin, pos, minDistance, rayColor).rgb / float(step_count); } #else - rayColor = vec4(light.Color.rgb, 1.0); - if(TracePoint(origin, light.Origin, minDistance, dir, dist)) - { - incoming.rgb += (rayColor.rgb * rayColor.w) * (attenuation * light.Intensity); - } + incoming.rgb += TracePointLightRay(origin, light.Origin, minDistance, rayColor).rgb; #endif } } @@ -61,19 +53,36 @@ vec3 TraceLight(vec3 origin, vec3 normal, LightInfo light, int surfaceIndex) return incoming; } -vec2 getVogelDiskSample(int sampleIndex, int sampleCount, float phi) +vec4 TracePointLightRay(vec3 origin, vec3 lightpos, float tmin, vec4 rayColor) { - const float goldenAngle = radians(180.0) * (3.0 - sqrt(5.0)); - float sampleIndexF = float(sampleIndex); - float sampleCountF = float(sampleCount); - - float r = sqrt((sampleIndexF + 0.5) / sampleCountF); // Assuming index and count are positive - float theta = sampleIndexF * goldenAngle + phi; - - float sine = sin(theta); - float cosine = cos(theta); - - return vec2(cosine, sine) * r; + vec3 dir = normalize(lightpos - origin); + float tmax = distance(origin, lightpos); + + for (int i = 0; i < 3; i++) + { + TraceResult result = TraceFirstHit(origin, tmin, dir, tmax); + + // We hit nothing. Point light is visible. + if (result.primitiveIndex == -1) + return rayColor; + + SurfaceInfo surface = GetSurface(result.primitiveIndex); + + // Blend with surface texture + rayColor = BlendTexture(surface, GetSurfaceUV(result.primitiveIndex, result.primitiveWeights), rayColor); + + // Stop if it isn't a portal, or there is no light left + if (surface.PortalIndex == 0 || rayColor.r + rayColor.g + rayColor.b <= 0.0) + return vec4(0.0); + + // Move to surface hit point + origin += dir * result.t; + tmax -= result.t; + + // Move through the portal, if any + TransformRay(surface.PortalIndex, origin, dir); + } + return vec4(0.0); } )glsl"; diff --git a/src/lightmapper/glsl/trace_sunlight.glsl.h b/src/lightmapper/glsl/trace_sunlight.glsl.h index f7a8e50..4febd86 100644 --- a/src/lightmapper/glsl/trace_sunlight.glsl.h +++ b/src/lightmapper/glsl/trace_sunlight.glsl.h @@ -1,21 +1,21 @@ static const char* trace_sunlight_glsl = R"glsl( -vec2 getVogelDiskSample(int sampleIndex, int sampleCount, float phi); +#include -vec3 TraceSunLight(vec3 origin, vec3 normal, int surfaceIndex) +vec4 TraceSunRay(vec3 origin, float tmin, vec3 dir, float tmax, vec4 rayColor); + +vec3 TraceSunLight(vec3 origin, vec3 normal) { - float angleAttenuation = 1.0f; - if (surfaceIndex >= 0) - { - angleAttenuation = max(dot(normal, SunDir), 0.0); - if (angleAttenuation == 0.0) - return vec3(0.0); - } + float angleAttenuation = max(dot(normal, SunDir), 0.0); + if (angleAttenuation == 0.0) + return vec3(0.0); const float minDistance = 0.01; vec3 incoming = vec3(0.0); const float dist = 65536.0; + vec4 rayColor = vec4(SunColor.rgb * SunIntensity, 1.0); + #if defined(USE_SOFTSHADOWS) vec3 target = origin + SunDir * dist; @@ -30,31 +30,47 @@ vec3 TraceSunLight(vec3 origin, vec3 normal, int surfaceIndex) { vec2 gridoffset = getVogelDiskSample(i, step_count, gl_FragCoord.x + gl_FragCoord.y * 13.37) * lightsize; vec3 pos = target + xdir * gridoffset.x + ydir * gridoffset.y; - - rayColor = vec4(SunColor.rgb * SunIntensity, 1.0); - - int primitiveID = TraceFirstHitTriangle(origin, minDistance, normalize(pos - origin), dist); - if (primitiveID != -1) - { - SurfaceInfo surface = surfaces[surfaceIndices[primitiveID]]; - incoming.rgb += rayColor.rgb * rayColor.w * surface.Sky / float(step_count); - } + incoming.rgb += TraceSunRay(origin, minDistance, normalize(pos - origin), dist, rayColor).rgb / float(step_count); } #else - rayColor = vec4(SunColor.rgb * SunIntensity, 1.0); - - int primitiveID = TraceFirstHitTriangle(origin, minDistance, SunDir, dist); - if (primitiveID != -1) - { - SurfaceInfo surface = surfaces[surfaceIndices[primitiveID]]; - incoming.rgb = rayColor.rgb * rayColor.w * surface.Sky; - } + incoming.rgb = TraceSunRay(origin, minDistance, SunDir, dist, rayColor).rgb; #endif return incoming * angleAttenuation; } +vec4 TraceSunRay(vec3 origin, float tmin, vec3 dir, float tmax, vec4 rayColor) +{ + for (int i = 0; i < 3; i++) + { + TraceResult result = TraceFirstHit(origin, tmin, dir, tmax); + + // We hit nothing. We have to hit a sky surface to hit the sky. + if (result.primitiveIndex == -1) + return vec4(0.0); + + SurfaceInfo surface = GetSurface(result.primitiveIndex); + + // Blend with surface texture + rayColor = BlendTexture(surface, GetSurfaceUV(result.primitiveIndex, result.primitiveWeights), rayColor); + + // Stop if it isn't a portal, or there is no light left + if (surface.PortalIndex == 0 || rayColor.r + rayColor.g + rayColor.b <= 0.0) + return rayColor * surface.Sky; + + // Move to surface hit point + origin += dir * result.t; + tmax -= result.t; + if (tmax <= tmin) + return vec4(0.0); + + // Move through the portal, if any + TransformRay(surface.PortalIndex, origin, dir); + } + return vec4(0.0); +} + )glsl"; diff --git a/src/lightmapper/glsl/vert_raytrace.glsl.h b/src/lightmapper/glsl/vert_raytrace.glsl.h index 04e889e..16e5dbb 100644 --- a/src/lightmapper/glsl/vert_raytrace.glsl.h +++ b/src/lightmapper/glsl/vert_raytrace.glsl.h @@ -5,13 +5,10 @@ static const char* vert_raytrace_glsl = R"glsl( layout(location = 0) in vec3 aPosition; layout(location = 0) out vec3 worldpos; -#if defined(USE_DRAWINDIRECT) layout(location = 1) out flat int InstanceIndex; -#endif void main() { -#if defined(USE_DRAWINDIRECT) vec3 WorldToLocal = constants[gl_InstanceIndex].WorldToLocal; float TextureSize = constants[gl_InstanceIndex].TextureSize; vec3 ProjLocalToU = constants[gl_InstanceIndex].ProjLocalToU; @@ -21,7 +18,6 @@ void main() float TileWidth = constants[gl_InstanceIndex].TileWidth; float TileHeight = constants[gl_InstanceIndex].TileHeight; InstanceIndex = gl_InstanceIndex; -#endif worldpos = aPosition; @@ -34,11 +30,10 @@ void main() gl_Position = vec4(vec2(TileX + x, TileY + y) / TextureSize * 2.0 - 1.0, 0.0, 1.0); // Clip all surfaces to the edge of the tile (effectly we are applying a viewport/scissor to the tile) - // Note: the tile has a 1px border around it that we also draw into - gl_ClipDistance[0] = x + 1.0; - gl_ClipDistance[1] = y + 1.0; - gl_ClipDistance[2] = TileWidth + 1.0 - x; - gl_ClipDistance[3] = TileHeight + 1.0 - y; + gl_ClipDistance[0] = x; + gl_ClipDistance[1] = y; + gl_ClipDistance[2] = TileWidth - x; + gl_ClipDistance[3] = TileHeight - y; } )glsl"; diff --git a/src/lightmapper/gpuraytracer.cpp b/src/lightmapper/gpuraytracer.cpp index ffc80bc..2c169bc 100644 --- a/src/lightmapper/gpuraytracer.cpp +++ b/src/lightmapper/gpuraytracer.cpp @@ -36,11 +36,10 @@ void GPURaytracer::Raytrace(DoomLevelMesh* mesh) { auto levelmesh = mDevice->GetLevelMesh(); auto lightmapper = mDevice->GetLightmapper(); - auto submesh = mesh->StaticMesh.get(); - printf(" Map uses %u lightmap textures\n", submesh->LMTextureCount); + printf(" Map uses %u lightmap textures\n", mesh->LMTextureCount); - mDevice->GetTextureManager()->CreateLightmap(submesh->LMTextureSize, submesh->LMTextureCount); + mDevice->GetTextureManager()->CreateLightmap(mesh->LMTextureSize, mesh->LMTextureCount); levelmesh->SetLevelMesh(mesh); lightmapper->SetLevelMesh(mesh); @@ -52,9 +51,9 @@ void GPURaytracer::Raytrace(DoomLevelMesh* mesh) lightmapper->BeginFrame(); TArray tiles; - for (unsigned int i = 0, count = submesh->LightmapTiles.Size(); i < count; i++) + for (unsigned int i = 0, count = mesh->LightmapTiles.Size(); i < count; i++) { - LightmapTile* tile = &submesh->LightmapTiles[i]; + LightmapTile* tile = &mesh->LightmapTiles[i]; if (tile->NeedsUpdate) { tiles.Push(tile); @@ -64,19 +63,19 @@ void GPURaytracer::Raytrace(DoomLevelMesh* mesh) if (tiles.Size() == 0) break; - printf(" Ray tracing tiles: %u / %u\r", submesh->LightmapTiles.Size() - tiles.Size(), submesh->LightmapTiles.Size()); + printf(" Ray tracing tiles: %u / %u\r", mesh->LightmapTiles.Size() - tiles.Size(), mesh->LightmapTiles.Size()); lightmapper->Raytrace(tiles); mDevice->GetCommands()->SubmitAndWait(); } - printf(" Ray tracing tiles: %u / %u\n", submesh->LightmapTiles.Size(), submesh->LightmapTiles.Size()); + printf(" Ray tracing tiles: %u / %u\n", mesh->LightmapTiles.Size(), mesh->LightmapTiles.Size()); - submesh->LMTextureData.Resize(submesh->LMTextureSize * submesh->LMTextureSize * submesh->LMTextureCount * 4); - for (int arrayIndex = 0; arrayIndex < submesh->LMTextureCount; arrayIndex++) + mesh->LMTextureData.Resize(mesh->LMTextureSize * mesh->LMTextureSize * mesh->LMTextureCount * 4); + for (int arrayIndex = 0; arrayIndex < mesh->LMTextureCount; arrayIndex++) { - mDevice->GetTextureManager()->DownloadLightmap(arrayIndex, submesh->LMTextureData.Data() + arrayIndex * submesh->LMTextureSize * submesh->LMTextureSize * 4); + mDevice->GetTextureManager()->DownloadLightmap(arrayIndex, mesh->LMTextureData.Data() + arrayIndex * mesh->LMTextureSize * mesh->LMTextureSize * 4); } } catch (...) diff --git a/src/lightmapper/hw_levelmesh.cpp b/src/lightmapper/hw_levelmesh.cpp index ae3f3f5..91bdaf0 100644 --- a/src/lightmapper/hw_levelmesh.cpp +++ b/src/lightmapper/hw_levelmesh.cpp @@ -6,6 +6,31 @@ LevelMesh::LevelMesh() // Default portal LevelMeshPortal portal; Portals.Push(portal); + + AddEmptyMesh(); + UpdateCollision(); +} + +void LevelMesh::AddEmptyMesh() +{ + // Default empty mesh (we can't make it completely empty since vulkan doesn't like that) + float minval = -100001.0f; + float maxval = -100000.0f; + Mesh.Vertices.Push({ minval, minval, minval }); + Mesh.Vertices.Push({ maxval, minval, minval }); + Mesh.Vertices.Push({ maxval, maxval, minval }); + Mesh.Vertices.Push({ minval, minval, minval }); + Mesh.Vertices.Push({ minval, maxval, minval }); + Mesh.Vertices.Push({ maxval, maxval, minval }); + Mesh.Vertices.Push({ minval, minval, maxval }); + Mesh.Vertices.Push({ maxval, minval, maxval }); + Mesh.Vertices.Push({ maxval, maxval, maxval }); + Mesh.Vertices.Push({ minval, minval, maxval }); + Mesh.Vertices.Push({ minval, maxval, maxval }); + Mesh.Vertices.Push({ maxval, maxval, maxval }); + + for (int i = 0; i < 3 * 4; i++) + Mesh.Indexes.Push(i); } LevelMeshSurface* LevelMesh::Trace(const FVector3& start, FVector3 direction, float maxDist) @@ -20,18 +45,14 @@ LevelMeshSurface* LevelMesh::Trace(const FVector3& start, FVector3 direction, fl { FVector3 end = origin + direction * maxDist; - TraceHit hit0 = TriangleMeshShape::find_first_hit(StaticMesh->Collision.get(), origin, end); - TraceHit hit1 = TriangleMeshShape::find_first_hit(DynamicMesh->Collision.get(), origin, end); - - LevelSubmesh* hitmesh = hit0.fraction < hit1.fraction ? StaticMesh.get() : DynamicMesh.get(); - TraceHit hit = hit0.fraction < hit1.fraction ? hit0 : hit1; + TraceHit hit = TriangleMeshShape::find_first_hit(Collision.get(), origin, end); if (hit.triangle < 0) { return nullptr; } - hitSurface = hitmesh->GetSurface(hitmesh->Mesh.SurfaceIndexes[hit.triangle]); + hitSurface = GetSurface(Mesh.SurfaceIndexes[hit.triangle]); int portal = hitSurface->PortalIndex; if (!portal) @@ -58,44 +79,6 @@ LevelMeshSurface* LevelMesh::Trace(const FVector3& start, FVector3 direction, fl LevelMeshTileStats LevelMesh::GatherTilePixelStats() { LevelMeshTileStats stats; - StaticMesh->GatherTilePixelStats(stats); - DynamicMesh->GatherTilePixelStats(stats); - return stats; -} - -///////////////////////////////////////////////////////////////////////////// - -LevelSubmesh::LevelSubmesh() -{ - // Default empty mesh (we can't make it completely empty since vulkan doesn't like that) - float minval = -100001.0f; - float maxval = -100000.0f; - Mesh.Vertices.Push({ minval, minval, minval }); - Mesh.Vertices.Push({ maxval, minval, minval }); - Mesh.Vertices.Push({ maxval, maxval, minval }); - Mesh.Vertices.Push({ minval, minval, minval }); - Mesh.Vertices.Push({ minval, maxval, minval }); - Mesh.Vertices.Push({ maxval, maxval, minval }); - Mesh.Vertices.Push({ minval, minval, maxval }); - Mesh.Vertices.Push({ maxval, minval, maxval }); - Mesh.Vertices.Push({ maxval, maxval, maxval }); - Mesh.Vertices.Push({ minval, minval, maxval }); - Mesh.Vertices.Push({ minval, maxval, maxval }); - Mesh.Vertices.Push({ maxval, maxval, maxval }); - - for (int i = 0; i < 3 * 4; i++) - Mesh.Indexes.Push(i); - - UpdateCollision(); -} - -void LevelSubmesh::UpdateCollision() -{ - Collision = std::make_unique(Mesh.Vertices.Data(), Mesh.Vertices.Size(), Mesh.Indexes.Data(), Mesh.Indexes.Size()); -} - -void LevelSubmesh::GatherTilePixelStats(LevelMeshTileStats& stats) -{ int count = GetSurfaceCount(); for (const LightmapTile& tile : LightmapTiles) { @@ -110,6 +93,12 @@ void LevelSubmesh::GatherTilePixelStats(LevelMeshTileStats& stats) } } stats.tiles.total += LightmapTiles.Size(); + return stats; +} + +void LevelMesh::UpdateCollision() +{ + Collision = std::make_unique(Mesh.Vertices.Data(), Mesh.Vertices.Size(), Mesh.Indexes.Data(), Mesh.Indexes.Size()); } struct LevelMeshPlaneGroup @@ -119,7 +108,7 @@ struct LevelMeshPlaneGroup std::vector surfaces; }; -void LevelSubmesh::BuildTileSurfaceLists() +void LevelMesh::BuildTileSurfaceLists() { // Plane group surface is to be rendered with TArray PlaneGroups; @@ -137,13 +126,13 @@ void LevelSubmesh::BuildTileSurfaceLists() if (surface->SectorGroup == PlaneGroups[j].sectorGroup) { float direction = PlaneGroups[j].plane.XYZ() | surface->Plane.XYZ(); - if (direction >= 0.9999f && direction <= 1.001f) + if (direction >= 0.999f && direction <= 1.01f) { auto point = (surface->Plane.XYZ() * surface->Plane.W); auto planeDistance = (PlaneGroups[j].plane.XYZ() | point) - PlaneGroups[j].plane.W; float dist = std::abs(planeDistance); - if (dist <= 0.01f) + if (dist <= 0.1f) { planeGroupIndex = (int)j; break; @@ -183,7 +172,105 @@ void LevelSubmesh::BuildTileSurfaceLists() if (surface != targetSurface && (maxUV.X < 0.0f || maxUV.Y < 0.0f || minUV.X > 1.0f || minUV.Y > 1.0f)) continue; // Bounding box not visible - tile->Surfaces.Push(surface); + tile->Surfaces.Push(GetSurfaceIndex(surface)); } } } + +void LevelMesh::SetupTileTransforms() +{ + for (auto& tile : LightmapTiles) + { + tile.SetupTileTransform(LMTextureSize); + } +} + +void LevelMesh::PackLightmapAtlas(int lightmapStartIndex) +{ + std::vector sortedTiles; + sortedTiles.reserve(LightmapTiles.Size()); + + for (auto& tile : LightmapTiles) + { + sortedTiles.push_back(&tile); + } + + 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; }); + + // We do not need to add spacing here as this is already built into the tile size itself. + RectPacker packer(LMTextureSize, LMTextureSize, RectPacker::Spacing(0), RectPacker::Padding(0)); + + for (LightmapTile* tile : sortedTiles) + { + auto result = packer.insert(tile->AtlasLocation.Width, tile->AtlasLocation.Height); + tile->AtlasLocation.X = result.pos.x; + tile->AtlasLocation.Y = result.pos.y; + tile->AtlasLocation.ArrayIndex = lightmapStartIndex + (int)result.pageIndex; + } + + LMTextureCount = (int)packer.getNumPages(); + + // Calculate final texture coordinates + for (int i = 0, count = GetSurfaceCount(); i < count; i++) + { + auto surface = GetSurface(i); + if (surface->LightmapTileIndex >= 0) + { + const LightmapTile& tile = LightmapTiles[surface->LightmapTileIndex]; + for (int i = 0; i < surface->MeshLocation.NumVerts; i++) + { + auto& vertex = Mesh.Vertices[surface->MeshLocation.StartVertIndex + i]; + FVector2 uv = tile.ToUV(vertex.fPos(), (float)LMTextureSize); + vertex.lu = uv.X; + vertex.lv = uv.Y; + vertex.lindex = (float)tile.AtlasLocation.ArrayIndex; + } + } + } + +#if 0 // Debug atlas tile locations: + float colors[30] = + { + 1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 1.0f, + 1.0f, 0.0f, 1.0f, + 0.5f, 0.0f, 0.0f, + 0.0f, 0.5f, 0.0f, + 0.5f, 0.5f, 0.0f, + 0.0f, 0.5f, 0.5f, + 0.5f, 0.0f, 0.5f + }; + LMTextureData.Resize(LMTextureSize * LMTextureSize * LMTextureCount * 3); + uint16_t* pixels = LMTextureData.Data(); + for (LightmapTile& tile : LightmapTiles) + { + tile.NeedsUpdate = false; + + int index = tile.Binding.TypeIndex; + float* color = colors + (index % 10) * 3; + + 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 + tile.AtlasLocation.ArrayIndex * LMTextureSize * LMTextureSize + yy * LMTextureSize * 3; + for (int xx = x; xx < x + w; xx++) + { + float gray = (yy - y) / (float)h; + line[xx * 3] = floatToHalf(color[0] * gray); + line[xx * 3 + 1] = floatToHalf(color[1] * gray); + line[xx * 3 + 2] = floatToHalf(color[2] * gray); + } + } + } + for (int i = 0, count = GetSurfaceCount(); i < count; i++) + { + auto surface = GetSurface(i); + surface->AlwaysUpdate = false; + } +#endif +} diff --git a/src/lightmapper/hw_levelmesh.h b/src/lightmapper/hw_levelmesh.h index 6a8b17a..6c1aeb6 100644 --- a/src/lightmapper/hw_levelmesh.h +++ b/src/lightmapper/hw_levelmesh.h @@ -13,6 +13,9 @@ #include "hw_surfaceuniforms.h" #include +#include +typedef dp::rect_pack::RectPacker RectPacker; + struct LevelMeshTileStats; struct LevelSubmeshDrawRange @@ -22,24 +25,54 @@ struct LevelSubmeshDrawRange int Count; }; -class LevelSubmesh +class LevelMesh { public: - LevelSubmesh(); - virtual ~LevelSubmesh() = default; + LevelMesh(); + virtual ~LevelMesh() = default; virtual LevelMeshSurface* GetSurface(int index) { return nullptr; } virtual unsigned int GetSurfaceIndex(const LevelMeshSurface* surface) const { return 0xffffffff; } virtual int GetSurfaceCount() { return 0; } + LevelMeshSurface* Trace(const FVector3& start, FVector3 direction, float maxDist); + + LevelMeshTileStats GatherTilePixelStats(); + + // Map defaults + FVector3 SunDirection = FVector3(0.0f, 0.0f, -1.0f); + FVector3 SunColor = FVector3(0.0f, 0.0f, 0.0f); + + TArray Portals; + struct { + // Vertex data TArray Vertices; - TArray Indexes; - TArray SurfaceIndexes; TArray UniformIndexes; + + // Surface info TArray Uniforms; TArray Materials; + TArray LightIndexes; + + // Lights + TArray Lights; + + // Index data + TArray Indexes; + TArray SurfaceIndexes; + int DynamicIndexStart = 0; + + // Above data must not be resized beyond these limits as that's the size of the GPU buffers) + int MaxVertices = 0; + int MaxIndexes = 0; + int MaxSurfaces = 0; + int MaxUniforms = 0; + int MaxSurfaceIndexes = 0; + int MaxNodes = 0; + int MaxLights = 0; + int MaxLightIndexes = 0; } Mesh; std::unique_ptr Collision; @@ -59,33 +92,11 @@ public: uint32_t AtlasPixelCount() const { return uint32_t(LMTextureCount * LMTextureSize * LMTextureSize); } void UpdateCollision(); - void GatherTilePixelStats(LevelMeshTileStats& stats); void BuildTileSurfaceLists(); + void SetupTileTransforms(); + void PackLightmapAtlas(int lightmapStartIndex); -private: - FVector2 ToUV(const FVector3& vert, const LightmapTile* tile); -}; - -class LevelMesh -{ -public: - LevelMesh(); - virtual ~LevelMesh() = default; - - std::unique_ptr StaticMesh = std::make_unique(); - std::unique_ptr DynamicMesh = std::make_unique(); - - virtual int AddSurfaceLights(const LevelMeshSurface* surface, LevelMeshLight* list, int listMaxSize) { return 0; } - - LevelMeshSurface* Trace(const FVector3& start, FVector3 direction, float maxDist); - - LevelMeshTileStats GatherTilePixelStats(); - - // Map defaults - FVector3 SunDirection = FVector3(0.0f, 0.0f, -1.0f); - FVector3 SunColor = FVector3(0.0f, 0.0f, 0.0f); - - TArray Portals; + void AddEmptyMesh(); }; struct LevelMeshTileStats diff --git a/src/lightmapper/hw_levelmeshsurface.h b/src/lightmapper/hw_levelmeshsurface.h index 70b4ccf..52f1e29 100644 --- a/src/lightmapper/hw_levelmeshsurface.h +++ b/src/lightmapper/hw_levelmeshsurface.h @@ -9,11 +9,10 @@ class LevelSubmesh; struct LevelMeshSurface; +struct ThingLight; struct LevelMeshSurface { - LevelSubmesh* Submesh = nullptr; - struct { unsigned int StartVertIndex = 0; @@ -28,9 +27,9 @@ struct LevelMeshSurface bool AlwaysUpdate = false; - FTextureID Texture = FNullTextureID(); + FTextureID Texture = FNullTextureID(); // FGameTexture* Texture = nullptr; float Alpha = 1.0; - + bool IsSky = false; int PortalIndex = 0; int SectorGroup = 0; @@ -38,8 +37,9 @@ struct LevelMeshSurface // Light list location in the lightmapper GPU buffers struct { - int Pos = -1; + int Pos = 0; int Count = 0; - int ResetCounter = -1; } LightList; + + TArray Lights; }; diff --git a/src/lightmapper/hw_lightmaptile.h b/src/lightmapper/hw_lightmaptile.h index 9262549..221750c 100644 --- a/src/lightmapper/hw_lightmaptile.h +++ b/src/lightmapper/hw_lightmaptile.h @@ -45,7 +45,7 @@ struct LightmapTile LightmapTileBinding Binding; // Surfaces that are visible within the lightmap tile - TArray Surfaces; + TArray Surfaces; BBox Bounds; uint16_t SampleDimension = 0; @@ -57,16 +57,114 @@ struct LightmapTile 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); + float u = (localPos | Transform.ProjLocalToU) / AtlasLocation.Width; + float v = (localPos | Transform.ProjLocalToV) / AtlasLocation.Height; return FVector2(u, v); } FVector2 ToUV(const FVector3& vert, float textureSize) const { + // Clamp in case the wall moved outside the tile (happens if a lift moves with a static lightmap on it) FVector3 localPos = vert - Transform.TranslateWorldToLocal; - float u = (AtlasLocation.X + (localPos | Transform.ProjLocalToU)) / textureSize; - float v = (AtlasLocation.Y + (localPos | Transform.ProjLocalToV)) / textureSize; + float u = std::max(std::min(localPos | Transform.ProjLocalToU, (float)AtlasLocation.Width), 0.0f); + float v = std::max(std::min(localPos | Transform.ProjLocalToV, (float)AtlasLocation.Height), 0.0f); + u = (AtlasLocation.X + u) / textureSize; + v = (AtlasLocation.Y + v) / textureSize; return FVector2(u, v); } + + enum PlaneAxis + { + AXIS_YZ = 0, + AXIS_XZ, + AXIS_XY + }; + + static PlaneAxis BestAxis(const FVector4& p) + { + float na = fabs(float(p.X)); + float nb = fabs(float(p.Y)); + float nc = fabs(float(p.Z)); + + // figure out what axis the plane lies on + if (na >= nb && na >= nc) + { + return AXIS_YZ; + } + else if (nb >= na && nb >= nc) + { + return AXIS_XZ; + } + + return AXIS_XY; + } + + void SetupTileTransform(int textureSize) + { + // These calculations align the tile so that there's a one texel border around the actual surface in the tile. + // + // This removes sampling artifacts as a linear sampler reads from a 2x2 area. + // The tile is also aligned to the grid to keep aliasing artifacts consistent. + + FVector3 uvMin; + uvMin.X = std::floor(Bounds.min.X / SampleDimension) - 1.0f; + uvMin.Y = std::floor(Bounds.min.Y / SampleDimension) - 1.0f; + uvMin.Z = std::floor(Bounds.min.Z / SampleDimension) - 1.0f; + + FVector3 uvMax; + uvMax.X = std::floor(Bounds.max.X / SampleDimension) + 2.0f; + uvMax.Y = std::floor(Bounds.max.Y / SampleDimension) + 2.0f; + uvMax.Z = std::floor(Bounds.max.Z / SampleDimension) + 2.0f; + + FVector3 tCoords[2] = { FVector3(0.0f, 0.0f, 0.0f), FVector3(0.0f, 0.0f, 0.0f) }; + int width, height; + switch (BestAxis(Plane)) + { + default: + case AXIS_YZ: + width = (int)(uvMax.Y - uvMin.Y); + height = (int)(uvMax.Z - uvMin.Z); + tCoords[0].Y = 1.0f / SampleDimension; + tCoords[1].Z = 1.0f / SampleDimension; + break; + + case AXIS_XZ: + width = (int)(uvMax.X - uvMin.X); + height = (int)(uvMax.Z - uvMin.Z); + tCoords[0].X = 1.0f / SampleDimension; + tCoords[1].Z = 1.0f / SampleDimension; + break; + + case AXIS_XY: + width = (int)(uvMax.X - uvMin.X); + height = (int)(uvMax.Y - uvMin.Y); + tCoords[0].X = 1.0f / SampleDimension; + tCoords[1].Y = 1.0f / SampleDimension; + break; + } + + textureSize -= 6; // Lightmapper needs some padding when baking + + // Tile can never be bigger than the texture. + if (width > textureSize) + { + tCoords[0] *= textureSize / (float)width; + width = textureSize; + } + if (height > textureSize) + { + tCoords[1] *= textureSize / (float)height; + height = textureSize; + } + + Transform.TranslateWorldToLocal.X = uvMin.X * SampleDimension; + Transform.TranslateWorldToLocal.Y = uvMin.Y * SampleDimension; + Transform.TranslateWorldToLocal.Z = uvMin.Z * SampleDimension; + + Transform.ProjLocalToU = tCoords[0]; + Transform.ProjLocalToV = tCoords[1]; + + AtlasLocation.Width = width; + AtlasLocation.Height = height; + } }; diff --git a/src/lightmapper/vk_levelmesh.cpp b/src/lightmapper/vk_levelmesh.cpp index 191e8b1..087b585 100644 --- a/src/lightmapper/vk_levelmesh.cpp +++ b/src/lightmapper/vk_levelmesh.cpp @@ -27,7 +27,7 @@ VkLevelMesh::VkLevelMesh(VulkanRenderDevice* fb) : fb(fb) { - useRayQuery = fb->GetDevice()->SupportsExtension(VK_KHR_RAY_QUERY_EXTENSION_NAME) && fb->GetDevice()->PhysicalDevice.Features.RayQuery.rayQuery; + useRayQuery = fb->IsRayQueryEnabled(); SetLevelMesh(nullptr); } @@ -52,6 +52,8 @@ void VkLevelMesh::Reset() deletelist->Add(std::move(UniformsBuffer)); deletelist->Add(std::move(SurfaceIndexBuffer)); deletelist->Add(std::move(PortalBuffer)); + deletelist->Add(std::move(LightBuffer)); + deletelist->Add(std::move(LightIndexBuffer)); deletelist->Add(std::move(StaticBLAS.ScratchBuffer)); deletelist->Add(std::move(StaticBLAS.AccelStructBuffer)); deletelist->Add(std::move(StaticBLAS.AccelStruct)); @@ -89,7 +91,7 @@ void VkLevelMesh::CreateVulkanObjects() .AddMemory(VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR | VK_ACCESS_SHADER_READ_BIT) .Execute(fb->GetCommands()->GetTransferCommands(), VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR | VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR); - CreateTopLevelAS(); + CreateTopLevelAS(DynamicBLAS.AccelStruct ? 2 : 1); // Finish building the accel struct before using it from the shaders PipelineBarrier() @@ -126,7 +128,8 @@ void VkLevelMesh::BeginFrame() deletelist->Add(std::move(TopLevelAS.TransferBuffer)); deletelist->Add(std::move(TopLevelAS.InstanceBuffer)); - DynamicBLAS = CreateBLAS(Mesh->DynamicMesh.get(), true, Mesh->StaticMesh->Mesh.Vertices.Size(), Mesh->StaticMesh->Mesh.Indexes.Size()); + if (Mesh->Mesh.DynamicIndexStart < (int)Mesh->Mesh.Indexes.Size()) + DynamicBLAS = CreateBLAS(true, Mesh->Mesh.DynamicIndexStart, Mesh->Mesh.Indexes.Size() - Mesh->Mesh.DynamicIndexStart); CreateTLASInstanceBuffer(); UploadTLASInstanceBuffer(); @@ -136,7 +139,7 @@ void VkLevelMesh::BeginFrame() .AddMemory(VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR | VK_ACCESS_SHADER_READ_BIT) .Execute(fb->GetCommands()->GetTransferCommands(), VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR | VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR); - UpdateTopLevelAS(); + UpdateTopLevelAS(DynamicBLAS.AccelStruct ? 2 : 1); // Finish building the accel struct before using it from the shaders PipelineBarrier() @@ -154,38 +157,27 @@ void VkLevelMesh::BeginFrame() void VkLevelMesh::UploadMeshes(bool dynamicOnly) { + if (dynamicOnly) + { + Locations.Index.Push({ Mesh->Mesh.DynamicIndexStart, (int)(Mesh->Mesh.Indexes.Size() - Mesh->Mesh.DynamicIndexStart) }); + } + else + { + if (!useRayQuery) + Locations.Node.Push({ 0, (int)Mesh->Collision->get_nodes().size() }); + Locations.Vertex.Push({ 0, (int)Mesh->Mesh.Vertices.Size() }); + Locations.Index.Push({ 0, (int)Mesh->Mesh.Indexes.Size() }); + Locations.SurfaceIndex.Push({ 0, (int)Mesh->Mesh.SurfaceIndexes.Size() }); + Locations.Surface.Push({ 0, Mesh->GetSurfaceCount() }); + Locations.UniformIndexes.Push({ 0, (int)Mesh->Mesh.UniformIndexes.Size() }); + Locations.Uniforms.Push({ 0, (int)Mesh->Mesh.Uniforms.Size() }); + Locations.Portals.Push({ 0, (int)Mesh->Portals.Size() }); + Locations.Light.Push({ 0, (int)Mesh->Mesh.Lights.Size() }); + Locations.LightIndex.Push({ 0, (int)Mesh->Mesh.LightIndexes.Size() }); + } + VkLevelMeshUploader uploader(this); - uploader.Upload(dynamicOnly); -} - -int VkLevelMesh::GetMaxVertexBufferSize() -{ - return Mesh->StaticMesh->Mesh.Vertices.Size() + MaxDynamicVertices; -} - -int VkLevelMesh::GetMaxIndexBufferSize() -{ - return Mesh->StaticMesh->Mesh.Indexes.Size() + MaxDynamicIndexes; -} - -int VkLevelMesh::GetMaxNodeBufferSize() -{ - return (int)Mesh->StaticMesh->Collision->get_nodes().size() + MaxDynamicNodes + 1; // + 1 for the merge root node -} - -int VkLevelMesh::GetMaxSurfaceBufferSize() -{ - return Mesh->StaticMesh->GetSurfaceCount() + MaxDynamicSurfaces; -} - -int VkLevelMesh::GetMaxUniformsBufferSize() -{ - return Mesh->StaticMesh->Mesh.Uniforms.Size() + MaxDynamicUniforms; -} - -int VkLevelMesh::GetMaxSurfaceIndexBufferSize() -{ - return Mesh->StaticMesh->Mesh.SurfaceIndexes.Size() + MaxDynamicSurfaceIndexes; + uploader.Upload(); } void VkLevelMesh::CreateBuffers() @@ -198,7 +190,7 @@ void VkLevelMesh::CreateBuffers() VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR : 0) | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT) - .Size(GetMaxVertexBufferSize() * sizeof(FFlatVertex)) + .Size(Mesh->Mesh.MaxVertices * sizeof(FFlatVertex)) .DebugName("VertexBuffer") .Create(fb->GetDevice()); @@ -206,7 +198,7 @@ void VkLevelMesh::CreateBuffers() .Usage( VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT) - .Size(GetMaxVertexBufferSize() * sizeof(int)) + .Size(Mesh->Mesh.MaxVertices * sizeof(int)) .DebugName("UniformIndexes") .Create(fb->GetDevice()); @@ -218,31 +210,31 @@ void VkLevelMesh::CreateBuffers() VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR : 0) | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT) - .Size((size_t)GetMaxIndexBufferSize() * sizeof(uint32_t)) + .Size((size_t)Mesh->Mesh.MaxIndexes * sizeof(uint32_t)) .DebugName("IndexBuffer") .Create(fb->GetDevice()); NodeBuffer = BufferBuilder() .Usage(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT) - .Size(sizeof(CollisionNodeBufferHeader) + GetMaxNodeBufferSize() * sizeof(CollisionNode)) + .Size(sizeof(CollisionNodeBufferHeader) + Mesh->Mesh.MaxNodes * sizeof(CollisionNode)) .DebugName("NodeBuffer") .Create(fb->GetDevice()); SurfaceIndexBuffer = BufferBuilder() .Usage(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT) - .Size(GetMaxSurfaceIndexBufferSize() * sizeof(int)) + .Size(Mesh->Mesh.MaxSurfaceIndexes * sizeof(int)) .DebugName("SurfaceBuffer") .Create(fb->GetDevice()); SurfaceBuffer = BufferBuilder() .Usage(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT) - .Size(GetMaxSurfaceBufferSize() * sizeof(SurfaceInfo)) + .Size(Mesh->Mesh.MaxSurfaces * sizeof(SurfaceInfo)) .DebugName("SurfaceBuffer") .Create(fb->GetDevice()); UniformsBuffer = BufferBuilder() .Usage(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT) - .Size(GetMaxUniformsBufferSize() * sizeof(SurfaceUniforms)) + .Size(Mesh->Mesh.MaxUniforms * sizeof(SurfaceUniforms)) .DebugName("SurfaceUniformsBuffer") .Create(fb->GetDevice()); @@ -251,9 +243,21 @@ void VkLevelMesh::CreateBuffers() .Size(Mesh->Portals.Size() * sizeof(PortalInfo)) .DebugName("PortalBuffer") .Create(fb->GetDevice()); + + LightBuffer = BufferBuilder() + .Usage(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT) + .Size(Mesh->Mesh.MaxLights * sizeof(LightInfo)) + .DebugName("LightBuffer") + .Create(fb->GetDevice()); + + LightIndexBuffer = BufferBuilder() + .Usage(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT) + .Size(Mesh->Mesh.MaxLightIndexes * sizeof(int32_t)) + .DebugName("LightIndexBuffer") + .Create(fb->GetDevice()); } -VkLevelMesh::BLAS VkLevelMesh::CreateBLAS(LevelSubmesh* submesh, bool preferFastBuild, int vertexOffset, int indexOffset) +VkLevelMesh::BLAS VkLevelMesh::CreateBLAS(bool preferFastBuild, int indexOffset, int indexCount) { BLAS blas; @@ -269,7 +273,7 @@ VkLevelMesh::BLAS VkLevelMesh::CreateBLAS(LevelSubmesh* submesh, bool preferFast accelStructBLDesc.geometry.triangles.vertexStride = sizeof(FFlatVertex); accelStructBLDesc.geometry.triangles.indexType = VK_INDEX_TYPE_UINT32; accelStructBLDesc.geometry.triangles.indexData.deviceAddress = IndexBuffer->GetDeviceAddress() + indexOffset * sizeof(uint32_t); - accelStructBLDesc.geometry.triangles.maxVertex = vertexOffset + submesh->Mesh.Vertices.Size() - 1; + accelStructBLDesc.geometry.triangles.maxVertex = Mesh->Mesh.Vertices.Size() - 1; buildInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR; buildInfo.flags = preferFastBuild ? VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_BUILD_BIT_KHR : VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR; @@ -277,7 +281,7 @@ VkLevelMesh::BLAS VkLevelMesh::CreateBLAS(LevelSubmesh* submesh, bool preferFast buildInfo.geometryCount = 1; buildInfo.ppGeometries = geometries; - uint32_t maxPrimitiveCount = submesh->Mesh.Indexes.Size() / 3; + uint32_t maxPrimitiveCount = indexCount / 3; VkAccelerationStructureBuildSizesInfoKHR sizeInfo = { VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR }; vkGetAccelerationStructureBuildSizesKHR(fb->GetDevice()->device, VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, &buildInfo, &maxPrimitiveCount, &sizeInfo); @@ -315,12 +319,13 @@ VkLevelMesh::BLAS VkLevelMesh::CreateBLAS(LevelSubmesh* submesh, bool preferFast void VkLevelMesh::CreateStaticBLAS() { - StaticBLAS = CreateBLAS(Mesh->StaticMesh.get(), false, 0, 0); + StaticBLAS = CreateBLAS(false, 0, Mesh->Mesh.DynamicIndexStart); } void VkLevelMesh::CreateDynamicBLAS() { - DynamicBLAS = CreateBLAS(Mesh->DynamicMesh.get(), true, Mesh->StaticMesh->Mesh.Vertices.Size(), Mesh->StaticMesh->Mesh.Indexes.Size()); + if (Mesh->Mesh.DynamicIndexStart < (int)Mesh->Mesh.Indexes.Size()) + DynamicBLAS = CreateBLAS(true, Mesh->Mesh.DynamicIndexStart, Mesh->Mesh.Indexes.Size() - Mesh->Mesh.DynamicIndexStart); } void VkLevelMesh::CreateTLASInstanceBuffer() @@ -338,7 +343,7 @@ void VkLevelMesh::CreateTLASInstanceBuffer() .Create(fb->GetDevice()); } -void VkLevelMesh::CreateTopLevelAS() +void VkLevelMesh::CreateTopLevelAS(int instanceCount) { VkAccelerationStructureBuildGeometryInfoKHR buildInfo = { VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR }; VkAccelerationStructureGeometryKHR accelStructTLDesc = { VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR }; @@ -383,12 +388,12 @@ void VkLevelMesh::CreateTopLevelAS() VkAccelerationStructureBuildRangeInfoKHR rangeInfo = {}; VkAccelerationStructureBuildRangeInfoKHR* rangeInfos[] = { &rangeInfo }; - rangeInfo.primitiveCount = 2; + rangeInfo.primitiveCount = instanceCount; fb->GetCommands()->GetTransferCommands()->buildAccelerationStructures(1, &buildInfo, rangeInfos); } -void VkLevelMesh::UpdateTopLevelAS() +void VkLevelMesh::UpdateTopLevelAS(int instanceCount) { VkAccelerationStructureBuildGeometryInfoKHR buildInfo = { VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR }; VkAccelerationStructureGeometryKHR accelStructTLDesc = { VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR }; @@ -409,7 +414,7 @@ void VkLevelMesh::UpdateTopLevelAS() VkAccelerationStructureBuildRangeInfoKHR rangeInfo = {}; VkAccelerationStructureBuildRangeInfoKHR* rangeInfos[] = { &rangeInfo }; - rangeInfo.primitiveCount = 2; + rangeInfo.primitiveCount = instanceCount; fb->GetCommands()->GetTransferCommands()->buildAccelerationStructures(1, &buildInfo, rangeInfos); } @@ -424,12 +429,15 @@ void VkLevelMesh::UploadTLASInstanceBuffer() instances[0].flags = 0; instances[0].accelerationStructureReference = StaticBLAS.AccelStruct->GetDeviceAddress(); - instances[1].transform.matrix[0][0] = 1.0f; - instances[1].transform.matrix[1][1] = 1.0f; - instances[1].transform.matrix[2][2] = 1.0f; - instances[1].mask = 0xff; - instances[1].flags = 0; - instances[1].accelerationStructureReference = DynamicBLAS.AccelStruct->GetDeviceAddress(); + if (DynamicBLAS.AccelStruct) + { + instances[1].transform.matrix[0][0] = 1.0f; + instances[1].transform.matrix[1][1] = 1.0f; + instances[1].transform.matrix[2][2] = 1.0f; + instances[1].mask = 0xff; + instances[1].flags = 0; + instances[1].accelerationStructureReference = DynamicBLAS.AccelStruct->GetDeviceAddress(); + } auto data = (uint8_t*)TopLevelAS.TransferBuffer->Map(0, sizeof(VkAccelerationStructureInstanceKHR) * 2); memcpy(data, instances, sizeof(VkAccelerationStructureInstanceKHR) * 2); @@ -444,30 +452,44 @@ VkLevelMeshUploader::VkLevelMeshUploader(VkLevelMesh* mesh) : Mesh(mesh) { } -void VkLevelMeshUploader::Upload(bool dynamicOnly) +void VkLevelMeshUploader::Upload() { - UpdateSizes(); - UpdateLocations(); - - start = dynamicOnly; - end = locations.Size(); - size_t transferBufferSize = GetTransferSize(); if (transferBufferSize == 0) + { + ClearRanges(); return; + } BeginTransfer(transferBufferSize); UploadNodes(); - UploadVertices(); - UploadUniformIndexes(); - UploadIndexes(); - UploadSurfaceIndexes(); + UploadRanges(Mesh->Locations.Vertex, Mesh->Mesh->Mesh.Vertices.Data(), Mesh->VertexBuffer.get()); + UploadRanges(Mesh->Locations.UniformIndexes, Mesh->Mesh->Mesh.UniformIndexes.Data(), Mesh->UniformIndexBuffer.get()); + UploadRanges(Mesh->Locations.Index, Mesh->Mesh->Mesh.Indexes.Data(), Mesh->IndexBuffer.get()); + UploadRanges(Mesh->Locations.SurfaceIndex, Mesh->Mesh->Mesh.SurfaceIndexes.Data(), Mesh->SurfaceIndexBuffer.get()); + UploadRanges(Mesh->Locations.LightIndex, Mesh->Mesh->Mesh.LightIndexes.Data(), Mesh->LightIndexBuffer.get()); UploadSurfaces(); UploadUniforms(); UploadPortals(); + UploadLights(); EndTransfer(transferBufferSize); + ClearRanges(); +} + +void VkLevelMeshUploader::ClearRanges() +{ + Mesh->Locations.Vertex.Clear(); + Mesh->Locations.Index.Clear(); + Mesh->Locations.Node.Clear(); + Mesh->Locations.SurfaceIndex.Clear(); + Mesh->Locations.Surface.Clear(); + Mesh->Locations.UniformIndexes.Clear(); + Mesh->Locations.Uniforms.Clear(); + Mesh->Locations.Portals.Clear(); + Mesh->Locations.Light.Clear(); + Mesh->Locations.LightIndex.Clear(); } void VkLevelMeshUploader::BeginTransfer(size_t transferBufferSize) @@ -498,146 +520,63 @@ static FVector3 SwapYZ(const FVector3& v) void VkLevelMeshUploader::UploadNodes() { - // Copy node buffer header and create a root node that merges the static and dynamic AABB trees - if (locations[1].Submesh->Collision->get_root() != -1) - { - int root0 = locations[0].Submesh->Collision->get_root(); - int root1 = locations[1].Submesh->Collision->get_root(); - const auto& node0 = locations[0].Submesh->Collision->get_nodes()[root0]; - const auto& node1 = locations[1].Submesh->Collision->get_nodes()[root1]; - - FVector3 aabbMin(std::min(node0.aabb.min.X, node1.aabb.min.X), std::min(node0.aabb.min.Y, node1.aabb.min.Y), std::min(node0.aabb.min.Z, node1.aabb.min.Z)); - FVector3 aabbMax(std::max(node0.aabb.max.X, node1.aabb.max.X), std::max(node0.aabb.max.Y, node1.aabb.max.Y), std::max(node0.aabb.max.Z, node1.aabb.max.Z)); - CollisionBBox bbox(aabbMin, aabbMax); - - CollisionNodeBufferHeader nodesHeader; - nodesHeader.root = locations[1].Node.Offset + locations[1].Node.Size; - - CollisionNode info; - info.center = SwapYZ(bbox.Center); - info.extents = SwapYZ(bbox.Extents); - info.left = locations[0].Node.Offset + root0; - info.right = locations[1].Node.Offset + root1; - info.element_index = -1; - - *((CollisionNodeBufferHeader*)(data + datapos)) = nodesHeader; - *((CollisionNode*)(data + datapos + sizeof(CollisionNodeBufferHeader))) = info; - - cmdbuffer->copyBuffer(transferBuffer.get(), Mesh->NodeBuffer.get(), datapos, 0, sizeof(CollisionNodeBufferHeader)); - cmdbuffer->copyBuffer(transferBuffer.get(), Mesh->NodeBuffer.get(), datapos + sizeof(CollisionNodeBufferHeader), sizeof(CollisionNodeBufferHeader) + nodesHeader.root * sizeof(CollisionNode), sizeof(CollisionNode)); - } - else // second submesh is empty, just point the header at the first one + // Always update the header struct of the collision storage buffer block if something changed + if (Mesh->Locations.Node.Size() > 0) { CollisionNodeBufferHeader nodesHeader; - nodesHeader.root = locations[0].Submesh->Collision->get_root(); + nodesHeader.root = Mesh->Mesh->Collision->get_root(); *((CollisionNodeBufferHeader*)(data + datapos)) = nodesHeader; cmdbuffer->copyBuffer(transferBuffer.get(), Mesh->NodeBuffer.get(), datapos, 0, sizeof(CollisionNodeBufferHeader)); + + datapos += sizeof(CollisionNodeBufferHeader) + sizeof(CollisionNode); } - datapos += sizeof(CollisionNodeBufferHeader) + sizeof(CollisionNode); // Copy collision nodes - for (unsigned int i = start; i < end; i++) + for (const MeshBufferRange& range : Mesh->Locations.Node) { - const SubmeshBufferLocation& cur = locations[i]; - auto submesh = cur.Submesh; - + const auto& srcnodes = Mesh->Mesh->Collision->get_nodes(); CollisionNode* nodes = (CollisionNode*)(data + datapos); - for (auto& node : submesh->Collision->get_nodes()) + for (int i = 0, count = range.Size; i < count; i++) { + const auto& node = srcnodes[range.Offset + i]; CollisionNode info; info.center = SwapYZ(node.aabb.Center); info.extents = SwapYZ(node.aabb.Extents); - info.left = node.left != -1 ? node.left + cur.Node.Offset : -1; - info.right = node.right != -1 ? node.right + cur.Node.Offset : -1; - info.element_index = node.element_index != -1 ? node.element_index + cur.Index.Offset : -1; + info.left = node.left; + info.right = node.right; + info.element_index = node.element_index; *(nodes++) = info; } - size_t copysize = submesh->Collision->get_nodes().size() * sizeof(CollisionNode); + size_t copysize = range.Size * sizeof(CollisionNode); if (copysize > 0) - cmdbuffer->copyBuffer(transferBuffer.get(), Mesh->NodeBuffer.get(), datapos, sizeof(CollisionNodeBufferHeader) + cur.Node.Offset * sizeof(CollisionNode), copysize); + cmdbuffer->copyBuffer(transferBuffer.get(), Mesh->NodeBuffer.get(), datapos, sizeof(CollisionNodeBufferHeader) + range.Offset * sizeof(CollisionNode), copysize); datapos += copysize; } } -void VkLevelMeshUploader::UploadVertices() +template +void VkLevelMeshUploader::UploadRanges(const TArray& ranges, const T* srcbuffer, VulkanBuffer* destbuffer) { - for (unsigned int i = start; i < end; i++) + for (const MeshBufferRange& range : ranges) { - const SubmeshBufferLocation& cur = locations[i]; - auto submesh = cur.Submesh; - - size_t copysize = submesh->Mesh.Vertices.Size() * sizeof(FFlatVertex); - memcpy(data + datapos, submesh->Mesh.Vertices.Data(), copysize); + size_t copysize = range.Size * sizeof(T); + memcpy(data + datapos, srcbuffer + range.Offset, copysize); if (copysize > 0) - cmdbuffer->copyBuffer(transferBuffer.get(), Mesh->VertexBuffer.get(), datapos, cur.Vertex.Offset * sizeof(FFlatVertex), copysize); - datapos += copysize; - } -} - -void VkLevelMeshUploader::UploadUniformIndexes() -{ - for (unsigned int i = start; i < end; i++) - { - const SubmeshBufferLocation& cur = locations[i]; - auto submesh = cur.Submesh; - - size_t copysize = submesh->Mesh.UniformIndexes.Size() * sizeof(int); - memcpy(data + datapos, submesh->Mesh.UniformIndexes.Data(), copysize); - if (copysize > 0) - cmdbuffer->copyBuffer(transferBuffer.get(), Mesh->UniformIndexBuffer.get(), datapos, cur.UniformIndexes.Offset * sizeof(int), copysize); - datapos += copysize; - } -} - -void VkLevelMeshUploader::UploadIndexes() -{ - for (unsigned int i = start; i < end; i++) - { - const SubmeshBufferLocation& cur = locations[i]; - auto submesh = cur.Submesh; - - uint32_t* indexes = (uint32_t*)(data + datapos); - for (int j = 0, count = submesh->Mesh.Indexes.Size(); j < count; ++j) - *(indexes++) = cur.Vertex.Offset + submesh->Mesh.Indexes[j]; - - size_t copysize = submesh->Mesh.Indexes.Size() * sizeof(uint32_t); - if (copysize > 0) - cmdbuffer->copyBuffer(transferBuffer.get(), Mesh->IndexBuffer.get(), datapos, cur.Index.Offset * sizeof(uint32_t), copysize); - datapos += copysize; - } -} - -void VkLevelMeshUploader::UploadSurfaceIndexes() -{ - for (unsigned int i = start; i < end; i++) - { - const SubmeshBufferLocation& cur = locations[i]; - auto submesh = cur.Submesh; - - int* indexes = (int*)(data + datapos); - for (int j = 0, count = submesh->Mesh.SurfaceIndexes.Size(); j < count; ++j) - *(indexes++) = cur.SurfaceIndex.Offset + submesh->Mesh.SurfaceIndexes[j]; - - size_t copysize = submesh->Mesh.SurfaceIndexes.Size() * sizeof(int); - if (copysize > 0) - cmdbuffer->copyBuffer(transferBuffer.get(), Mesh->SurfaceIndexBuffer.get(), datapos, cur.SurfaceIndex.Offset * sizeof(int), copysize); + cmdbuffer->copyBuffer(transferBuffer.get(), destbuffer, datapos, range.Offset * sizeof(T), copysize); datapos += copysize; } } void VkLevelMeshUploader::UploadSurfaces() { - for (unsigned int i = start; i < end; i++) + for (const MeshBufferRange& range : Mesh->Locations.Surface) { - const SubmeshBufferLocation& cur = locations[i]; - auto submesh = cur.Submesh; - SurfaceInfo* surfaces = (SurfaceInfo*)(data + datapos); - for (int j = 0, count = submesh->GetSurfaceCount(); j < count; ++j) + for (int j = 0, count = range.Size; j < count; j++) { - LevelMeshSurface* surface = submesh->GetSurface(j); + LevelMeshSurface* surface = Mesh->Mesh->GetSurface(range.Offset + j); SurfaceInfo info; info.Normal = FVector3(surface->Plane.X, surface->Plane.Z, surface->Plane.Y); @@ -646,144 +585,115 @@ void VkLevelMeshUploader::UploadSurfaces() info.Alpha = surface->Alpha; if (surface->Texture.isValid()) { -#ifdef NEEDS_PORTING - auto mat = FMaterial::ValidateTexture(surface->Texture, 0); - info.TextureIndex = Mesh->fb->GetBindlessTextureIndex(mat, CLAMP_NONE, 0); -#else - info.TextureIndex = 0; -#endif + info.TextureIndex = Mesh->fb->GetBindlessTextureIndex(surface->Texture); } else { info.TextureIndex = 0; } + info.LightStart = surface->LightList.Pos; + info.LightEnd = surface->LightList.Pos + surface->LightList.Count; *(surfaces++) = info; } - size_t copysize = submesh->GetSurfaceCount() * sizeof(SurfaceInfo); + size_t copysize = range.Size * sizeof(SurfaceInfo); if (copysize > 0) - cmdbuffer->copyBuffer(transferBuffer.get(), Mesh->SurfaceBuffer.get(), datapos, cur.Surface.Offset * sizeof(SurfaceInfo), copysize); + cmdbuffer->copyBuffer(transferBuffer.get(), Mesh->SurfaceBuffer.get(), datapos, range.Offset * sizeof(SurfaceInfo), copysize); datapos += copysize; } } void VkLevelMeshUploader::UploadUniforms() { - for (unsigned int i = start; i < end; i++) + for (const MeshBufferRange& range : Mesh->Locations.Uniforms) { - const SubmeshBufferLocation& cur = locations[i]; - auto submesh = cur.Submesh; - - for (int j = 0, count = submesh->Mesh.Uniforms.Size(); j < count; j++) + for (int j = 0, count = range.Size; j < count; j++) { - auto& surfaceUniforms = submesh->Mesh.Uniforms[j]; - auto& material = submesh->Mesh.Materials[j]; - if (material.mMaterial) + auto& surfaceUniforms = Mesh->Mesh->Mesh.Uniforms[range.Offset + j]; + auto& material = Mesh->Mesh->Mesh.Materials[range.Offset + j]; + /*if (material.mMaterial) { -#ifdef NEEDS_PORTING auto source = material.mMaterial->Source(); surfaceUniforms.uSpecularMaterial = { source->GetGlossiness(), source->GetSpecularLevel() }; surfaceUniforms.uTextureIndex = Mesh->fb->GetBindlessTextureIndex(material.mMaterial, material.mClampMode, material.mTranslation); -#else - surfaceUniforms.uTextureIndex = 0; -#endif } - else + else*/ { surfaceUniforms.uTextureIndex = 0; } } SurfaceUniforms* uniforms = (SurfaceUniforms*)(data + datapos); - size_t copysize = submesh->Mesh.Uniforms.Size() * sizeof(SurfaceUniforms); - memcpy(uniforms, submesh->Mesh.Uniforms.Data(), copysize); + size_t copysize = range.Size * sizeof(SurfaceUniforms); + memcpy(uniforms, Mesh->Mesh->Mesh.Uniforms.Data(), copysize); if (copysize > 0) - cmdbuffer->copyBuffer(transferBuffer.get(), Mesh->UniformsBuffer.get(), datapos, cur.Uniforms.Offset * sizeof(SurfaceUniforms), copysize); + cmdbuffer->copyBuffer(transferBuffer.get(), Mesh->UniformsBuffer.get(), datapos, range.Offset * sizeof(SurfaceUniforms), copysize); datapos += copysize; } } void VkLevelMeshUploader::UploadPortals() { - if (start == 0) + for (const MeshBufferRange& range : Mesh->Locations.Portals) { PortalInfo* portals = (PortalInfo*)(data + datapos); - for (auto& portal : Mesh->Mesh->Portals) + for (int i = 0, count = range.Size; i < count; i++) { + const auto& portal = Mesh->Mesh->Portals[range.Offset + i]; PortalInfo info; info.transformation = portal.transformation; *(portals++) = info; } - size_t copysize = Mesh->Mesh->Portals.Size() * sizeof(PortalInfo); + size_t copysize = range.Size * sizeof(PortalInfo); if (copysize > 0) - cmdbuffer->copyBuffer(transferBuffer.get(), Mesh->PortalBuffer.get(), datapos, 0, copysize); + cmdbuffer->copyBuffer(transferBuffer.get(), Mesh->PortalBuffer.get(), datapos, range.Offset * sizeof(PortalInfo), copysize); datapos += copysize; } } -void VkLevelMeshUploader::UpdateSizes() +void VkLevelMeshUploader::UploadLights() { - for (LevelSubmesh* submesh : { Mesh->GetMesh()->StaticMesh.get(), Mesh->GetMesh()->DynamicMesh.get() }) + for (const MeshBufferRange& range : Mesh->Locations.Light) { - SubmeshBufferLocation location; - location.Submesh = submesh; - location.Vertex.Size = submesh->Mesh.Vertices.Size(); - location.Index.Size = submesh->Mesh.Indexes.Size(); - location.Node.Size = (int)submesh->Collision->get_nodes().size(); - location.SurfaceIndex.Size = submesh->Mesh.SurfaceIndexes.Size(); - location.Surface.Size = submesh->GetSurfaceCount(); - location.UniformIndexes.Size = submesh->Mesh.UniformIndexes.Size(); - location.Uniforms.Size = submesh->Mesh.Uniforms.Size(); - locations.Push(location); - } -} - -void VkLevelMeshUploader::UpdateLocations() -{ - for (unsigned int i = 1, count = locations.Size(); i < count; i++) - { - const SubmeshBufferLocation& prev = locations[i - 1]; - SubmeshBufferLocation& cur = locations[i]; - cur.Vertex.Offset = prev.Vertex.Offset + prev.Vertex.Size; - cur.Index.Offset = prev.Index.Offset + prev.Index.Size; - cur.Node.Offset = prev.Node.Offset + prev.Node.Size; - cur.SurfaceIndex.Offset = prev.SurfaceIndex.Offset + prev.SurfaceIndex.Size; - cur.Surface.Offset = prev.Surface.Offset + prev.Surface.Size; - cur.UniformIndexes.Offset = prev.UniformIndexes.Offset + prev.UniformIndexes.Size; - cur.Uniforms.Offset = prev.Uniforms.Offset + prev.Uniforms.Size; - - if ( - cur.Vertex.Offset + cur.Vertex.Size > Mesh->GetMaxVertexBufferSize() || - cur.Index.Offset + cur.Index.Size > Mesh->GetMaxIndexBufferSize() || - cur.Node.Offset + cur.Node.Size > Mesh->GetMaxNodeBufferSize() || - cur.SurfaceIndex.Offset + cur.SurfaceIndex.Size > Mesh->GetMaxSurfaceIndexBufferSize() || - cur.Surface.Offset + cur.Surface.Size > Mesh->GetMaxSurfaceBufferSize() || - cur.UniformIndexes.Offset + cur.UniformIndexes.Size > Mesh->GetMaxVertexBufferSize() || - cur.Uniforms.Offset + cur.Uniforms.Size > Mesh->GetMaxUniformsBufferSize()) + LightInfo* lights = (LightInfo*)(data + datapos); + for (int i = 0, count = range.Size; i < count; i++) { - I_FatalError("Dynamic accel struct buffers are too small!"); + const auto& light = Mesh->Mesh->Mesh.Lights[range.Offset + i]; + LightInfo info; + info.Origin = SwapYZ(light.Origin); + info.RelativeOrigin = SwapYZ(light.RelativeOrigin); + info.Radius = light.Radius; + info.Intensity = light.Intensity; + info.InnerAngleCos = light.InnerAngleCos; + info.OuterAngleCos = light.OuterAngleCos; + info.SpotDir = SwapYZ(light.SpotDir); + info.Color = light.Color; + *(lights++) = info; } + + size_t copysize = range.Size * sizeof(LightInfo); + if (copysize > 0) + cmdbuffer->copyBuffer(transferBuffer.get(), Mesh->LightBuffer.get(), datapos, range.Offset * sizeof(LightInfo), copysize); + datapos += copysize; } } size_t VkLevelMeshUploader::GetTransferSize() { // Figure out how much memory we need to transfer it to the GPU - size_t transferBufferSize = sizeof(CollisionNodeBufferHeader) + sizeof(CollisionNode); - for (unsigned int i = start; i < end; i++) - { - const SubmeshBufferLocation& cur = locations[i]; - transferBufferSize += cur.Submesh->Mesh.Vertices.Size() * sizeof(FFlatVertex); - transferBufferSize += cur.Submesh->Mesh.UniformIndexes.Size() * sizeof(int); - transferBufferSize += cur.Submesh->Mesh.Indexes.Size() * sizeof(uint32_t); - transferBufferSize += cur.Submesh->Collision->get_nodes().size() * sizeof(CollisionNode); - transferBufferSize += cur.Submesh->Mesh.SurfaceIndexes.Size() * sizeof(int); - transferBufferSize += cur.Submesh->GetSurfaceCount() * sizeof(SurfaceInfo); - transferBufferSize += cur.Submesh->Mesh.Uniforms.Size() * sizeof(SurfaceUniforms); - } - if (start == 0) - transferBufferSize += Mesh->GetMesh()->Portals.Size() * sizeof(PortalInfo); + size_t transferBufferSize = 0; + if (Mesh->Locations.Node.Size() > 0) transferBufferSize += sizeof(CollisionNodeBufferHeader) + sizeof(CollisionNode); + for (const MeshBufferRange& range : Mesh->Locations.Node) transferBufferSize += range.Size * sizeof(CollisionNode); + for (const MeshBufferRange& range : Mesh->Locations.Vertex) transferBufferSize += range.Size * sizeof(FFlatVertex); + for (const MeshBufferRange& range : Mesh->Locations.UniformIndexes) transferBufferSize += range.Size * sizeof(int); + for (const MeshBufferRange& range : Mesh->Locations.Index) transferBufferSize += range.Size * sizeof(uint32_t); + for (const MeshBufferRange& range : Mesh->Locations.SurfaceIndex) transferBufferSize += range.Size * sizeof(int); + for (const MeshBufferRange& range : Mesh->Locations.Surface) transferBufferSize += range.Size * sizeof(SurfaceInfo); + for (const MeshBufferRange& range : Mesh->Locations.Uniforms) transferBufferSize += range.Size * sizeof(SurfaceUniforms); + for (const MeshBufferRange& range : Mesh->Locations.Portals) transferBufferSize += range.Size * sizeof(PortalInfo); + for (const MeshBufferRange& range : Mesh->Locations.LightIndex) transferBufferSize += range.Size * sizeof(int32_t); + for (const MeshBufferRange& range : Mesh->Locations.Light) transferBufferSize += range.Size * sizeof(LightInfo); return transferBufferSize; } diff --git a/src/lightmapper/vk_levelmesh.h b/src/lightmapper/vk_levelmesh.h index 520b802..9630bbf 100644 --- a/src/lightmapper/vk_levelmesh.h +++ b/src/lightmapper/vk_levelmesh.h @@ -34,7 +34,11 @@ struct SurfaceInfo uint32_t PortalIndex; int32_t TextureIndex; float Alpha; - float Padding; + float Padding0; + uint32_t LightStart; + uint32_t LightEnd; + uint32_t Padding1; + uint32_t Padding2; }; struct PortalInfo @@ -42,24 +46,30 @@ struct PortalInfo VSMatrix transformation; }; -struct SubmeshBufferRange +struct LightInfo +{ + FVector3 Origin; + float Padding0; + FVector3 RelativeOrigin; + float Padding1; + float Radius; + float Intensity; + float InnerAngleCos; + float OuterAngleCos; + FVector3 SpotDir; + float Padding2; + FVector3 Color; + float Padding3; +}; + +static_assert(sizeof(LightInfo) == sizeof(float) * 20); + +struct MeshBufferRange { int Offset = 0; int Size = 0; }; -struct SubmeshBufferLocation -{ - LevelSubmesh* Submesh = nullptr; - SubmeshBufferRange Vertex; - SubmeshBufferRange Index; - SubmeshBufferRange Node; - SubmeshBufferRange SurfaceIndex; - SubmeshBufferRange Surface; - SubmeshBufferRange UniformIndexes; - SubmeshBufferRange Uniforms; -}; - class VkLevelMesh { public: @@ -77,6 +87,8 @@ public: VulkanBuffer* GetSurfaceBuffer() { return SurfaceBuffer.get(); } VulkanBuffer* GetUniformsBuffer() { return UniformsBuffer.get(); } VulkanBuffer* GetPortalBuffer() { return PortalBuffer.get(); } + VulkanBuffer* GetLightBuffer() { return LightBuffer.get(); } + VulkanBuffer* GetLightIndexBuffer() { return LightIndexBuffer.get(); } LevelMesh* GetMesh() { return Mesh; } @@ -94,20 +106,13 @@ private: void CreateStaticBLAS(); void CreateDynamicBLAS(); void CreateTLASInstanceBuffer(); - void CreateTopLevelAS(); + void CreateTopLevelAS(int instanceCount); void UploadMeshes(bool dynamicOnly); void UploadTLASInstanceBuffer(); - void UpdateTopLevelAS(); + void UpdateTopLevelAS(int instanceCount); - BLAS CreateBLAS(LevelSubmesh *submesh, bool preferFastBuild, int vertexOffset, int indexOffset); - - int GetMaxVertexBufferSize(); - int GetMaxIndexBufferSize(); - int GetMaxNodeBufferSize(); - int GetMaxSurfaceBufferSize(); - int GetMaxUniformsBufferSize(); - int GetMaxSurfaceIndexBufferSize(); + BLAS CreateBLAS(bool preferFastBuild, int indexOffset, int indexCount); VulkanRenderDevice* fb = nullptr; @@ -116,6 +121,20 @@ private: LevelMesh NullMesh; LevelMesh* Mesh = nullptr; + struct + { + TArray Vertex; + TArray Index; + TArray Node; + TArray SurfaceIndex; + TArray Surface; + TArray UniformIndexes; + TArray Uniforms; + TArray Portals; + TArray Light; + TArray LightIndex; + } Locations; + std::unique_ptr VertexBuffer; std::unique_ptr UniformIndexBuffer; std::unique_ptr IndexBuffer; @@ -123,17 +142,11 @@ private: std::unique_ptr SurfaceBuffer; std::unique_ptr UniformsBuffer; std::unique_ptr PortalBuffer; + std::unique_ptr LightBuffer; + std::unique_ptr LightIndexBuffer; std::unique_ptr NodeBuffer; - TArray Vertices; - static const int MaxDynamicVertices = 100'000; - static const int MaxDynamicIndexes = 100'000; - static const int MaxDynamicSurfaces = 100'000; - static const int MaxDynamicUniforms = 100'000; - static const int MaxDynamicSurfaceIndexes = 25'000; - static const int MaxDynamicNodes = 10'000; - BLAS StaticBLAS; BLAS DynamicBLAS; @@ -154,27 +167,24 @@ class VkLevelMeshUploader public: VkLevelMeshUploader(VkLevelMesh* mesh); - void Upload(bool dynamicOnly); + void Upload(); private: void BeginTransfer(size_t transferBufferSize); void EndTransfer(size_t transferBufferSize); + size_t GetTransferSize(); + void ClearRanges(); + void UploadNodes(); - void UploadVertices(); - void UploadUniformIndexes(); - void UploadIndexes(); - void UploadSurfaceIndexes(); void UploadSurfaces(); void UploadUniforms(); void UploadPortals(); - void UpdateSizes(); - void UpdateLocations(); - size_t GetTransferSize(); + void UploadLights(); - VkLevelMesh* Mesh; - TArray locations; - unsigned int start = 0; - unsigned int end = 0; + template + void UploadRanges(const TArray& ranges, const T* srcbuffer, VulkanBuffer* destbuffer); + + VkLevelMesh* Mesh = nullptr; uint8_t* data = nullptr; size_t datapos = 0; VulkanCommandBuffer* cmdbuffer = nullptr; diff --git a/src/lightmapper/vk_lightmapper.cpp b/src/lightmapper/vk_lightmapper.cpp index 87e0064..2f795f6 100644 --- a/src/lightmapper/vk_lightmapper.cpp +++ b/src/lightmapper/vk_lightmapper.cpp @@ -12,8 +12,10 @@ #include "glsl/frag_copy.glsl.h" #include "glsl/frag_raytrace.glsl.h" #include "glsl/frag_resolve.glsl.h" +#include "glsl/montecarlo.glsl.h" #include "glsl/polyfill_rayquery.glsl.h" #include "glsl/trace_ambient_occlusion.glsl.h" +#include "glsl/trace_bounce.glsl.h" #include "glsl/trace_levelmesh.glsl.h" #include "glsl/trace_light.glsl.h" #include "glsl/trace_sunlight.glsl.h" @@ -29,17 +31,17 @@ bool lm_ao = true; bool lm_softshadows = true; bool lm_sunlight = true; bool lm_blur = true; +bool lm_bounce = true; VkLightmapper::VkLightmapper(VulkanRenderDevice* fb) : fb(fb) { - useRayQuery = fb->GetDevice()->SupportsExtension(VK_KHR_RAY_QUERY_EXTENSION_NAME) && fb->GetDevice()->PhysicalDevice.Features.RayQuery.rayQuery; + useRayQuery = fb->IsRayQueryEnabled(); templightlist.Resize(128); try { CreateUniformBuffer(); - CreateLightBuffer(); CreateTileBuffer(); CreateDrawIndexedBuffer(); @@ -64,8 +66,6 @@ VkLightmapper::~VkLightmapper() void VkLightmapper::ReleaseResources() { - if (lights.Buffer) - lights.Buffer->Unmap(); if (copytiles.Buffer) copytiles.Buffer->Unmap(); if (drawindexed.CommandsBuffer) @@ -82,8 +82,6 @@ void VkLightmapper::SetLevelMesh(LevelMesh* level) void VkLightmapper::BeginFrame() { - lights.Pos = 0; - lights.ResetCounter++; drawindexed.Pos = 0; } @@ -114,8 +112,8 @@ void VkLightmapper::SelectTiles(const TArray& tiles) bakeImage.maxY = 0; 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)); + // We use a 3 texel spacing between rectangles so that the blur pass will not pick up anything from a neighbour tile. + RectPacker packer(bakeImageSize, bakeImageSize, RectPacker::Spacing(3), RectPacker::Padding(3)); for (int i = 0, count = tiles.Size(); i < count; i++) { @@ -125,21 +123,25 @@ void VkLightmapper::SelectTiles(const TArray& tiles) continue; // Only grab surfaces until our bake texture is full - auto result = packer.insert(tile->AtlasLocation.Width + 2, tile->AtlasLocation.Height + 2); + auto result = packer.insert(tile->AtlasLocation.Width, tile->AtlasLocation.Height); if (result.pageIndex == 0) { SelectedTile selected; selected.Tile = tile; - selected.X = result.pos.x + 1; - selected.Y = result.pos.y + 1; + selected.X = result.pos.x; + selected.Y = result.pos.y; selectedTiles.Push(selected); - bakeImage.maxX = std::max(bakeImage.maxX, uint16_t(selected.X + tile->AtlasLocation.Width + spacing)); - bakeImage.maxY = std::max(bakeImage.maxY, uint16_t(selected.Y + tile->AtlasLocation.Height + spacing)); + bakeImage.maxX = std::max(bakeImage.maxX, uint16_t(result.pos.x + tile->AtlasLocation.Width)); + bakeImage.maxY = std::max(bakeImage.maxY, uint16_t(result.pos.y + tile->AtlasLocation.Height)); tile->NeedsUpdate = false; } } + + // Include the padding + bakeImage.maxX += 3; + bakeImage.maxY += 3; } void VkLightmapper::Render() @@ -150,7 +152,7 @@ void VkLightmapper::Render() RenderPassBegin() .RenderPass(raytrace.renderPass.get()) - .RenderArea(0, 0, bakeImageSize, bakeImageSize) + .RenderArea(0, 0, bakeImage.maxX, bakeImage.maxY) .Framebuffer(bakeImage.raytrace.Framebuffer.get()) .AddClearColor(0.0f, 0.0f, 0.0f, 0.0f) .Execute(cmdbuffer); @@ -169,10 +171,6 @@ void VkLightmapper::Render() viewport.height = (float)bakeImageSize; cmdbuffer->setViewport(0, 1, &viewport); - int dynamicSurfaceIndexOffset = mesh->StaticMesh->GetSurfaceCount(); - int dynamicFirstIndexOffset = mesh->StaticMesh->Mesh.Indexes.Size(); - LevelSubmesh* staticMesh = mesh->StaticMesh.get(); - for (int i = 0, count = selectedTiles.Size(); i < count; i++) { auto& selectedTile = selectedTiles[i]; @@ -191,59 +189,15 @@ void VkLightmapper::Render() bool buffersFull = false; // Paint all surfaces visible in the tile - for (LevelMeshSurface* surface : targetTile->Surfaces) + for (int surfaceIndex : targetTile->Surfaces) { - int surfaceIndexOffset = 0; - int firstIndexOffset = 0; - if (surface->Submesh != staticMesh) - { - surfaceIndexOffset = dynamicSurfaceIndexOffset; - firstIndexOffset = dynamicFirstIndexOffset; - } + LevelMeshSurface* surface = mesh->GetSurface(surfaceIndex); + pc.SurfaceIndex = surfaceIndex; - pc.SurfaceIndex = surfaceIndexOffset + surface->Submesh->GetSurfaceIndex(surface); - - if (surface->LightList.ResetCounter != lights.ResetCounter) - { - int lightCount = mesh->AddSurfaceLights(surface, templightlist.Data(), (int)templightlist.Size()); - - if (lights.Pos + lightCount > lights.BufferSize) - { - // Our light buffer is full. Postpone the rest. - buffersFull = true; - break; - } - - surface->LightList.Pos = lights.Pos; - surface->LightList.Count = lightCount; - surface->LightList.ResetCounter = lights.ResetCounter; - - LightInfo* lightinfo = &lights.Lights[lights.Pos]; - for (int i = 0; i < lightCount; i++) - { - const LevelMeshLight* light = &templightlist[i]; - lightinfo->Origin = SwapYZ(light->Origin); - lightinfo->RelativeOrigin = SwapYZ(light->RelativeOrigin); - lightinfo->Radius = light->Radius; - lightinfo->Intensity = light->Intensity; - lightinfo->InnerAngleCos = light->InnerAngleCos; - lightinfo->OuterAngleCos = light->OuterAngleCos; - lightinfo->SpotDir = SwapYZ(light->SpotDir); - lightinfo->Color = light->Color; - lightinfo++; - } - - lights.Pos += lightCount; - } - - pc.LightStart = surface->LightList.Pos; - pc.LightEnd = pc.LightStart + surface->LightList.Count; - -#ifdef USE_DRAWINDIRECT VkDrawIndexedIndirectCommand cmd; cmd.indexCount = surface->MeshLocation.NumElements; cmd.instanceCount = 1; - cmd.firstIndex = firstIndexOffset + surface->MeshLocation.StartElementIndex; + cmd.firstIndex = surface->MeshLocation.StartElementIndex; cmd.vertexOffset = 0; cmd.firstInstance = drawindexed.Pos; drawindexed.Constants[drawindexed.Pos] = pc; @@ -256,10 +210,6 @@ void VkLightmapper::Render() buffersFull = true; break; } -#else - cmdbuffer->pushConstants(raytrace.pipelineLayout.get(), VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(LightmapRaytracePC), &pc); - cmdbuffer->drawIndexed(surface->numElements, 1, surface->startElementIndex, 0, 0); -#endif } if (buffersFull) @@ -275,9 +225,7 @@ void VkLightmapper::Render() selectedTile.Rendered = true; } -#ifdef USE_DRAWINDIRECT cmdbuffer->drawIndexedIndirect(drawindexed.CommandsBuffer->buffer, 0, drawindexed.Pos, sizeof(VkDrawIndexedIndirectCommand)); -#endif cmdbuffer->endRenderPass(); @@ -539,10 +487,6 @@ void VkLightmapper::CreateShaders() traceprefix += "#extension GL_EXT_ray_query : require\r\n"; traceprefix += "#define USE_RAYQUERY\r\n"; } -#ifdef USE_DRAWINDIRECT - prefix += "#define USE_DRAWINDIRECT\r\n"; - traceprefix += "#define USE_DRAWINDIRECT\r\n"; -#endif auto onIncludeLocal = [](std::string headerName, std::string includerName, size_t depth) { return OnInclude(headerName.c_str(), includerName.c_str(), depth, false); }; auto onIncludeSystem = [](std::string headerName, std::string includerName, size_t depth) { return OnInclude(headerName.c_str(), includerName.c_str(), depth, true); }; @@ -574,7 +518,7 @@ void VkLightmapper::CreateShaders() .DebugName("VkLightmapper.VertCopy") .Create("VkLightmapper.VertCopy", fb->GetDevice()); - for (int i = 0; i < 8; i++) + for (int i = 0; i < 16; i++) { std::string defines = traceprefix; if (i & 1) @@ -583,6 +527,8 @@ void VkLightmapper::CreateShaders() defines += "#define USE_AO\n"; if (i & 4) defines += "#define USE_SUNLIGHT\n"; + if (i & 8) + defines += "#define USE_BOUNCE\n"; shaders.fragRaytrace[i] = ShaderBuilder() .Type(ShaderType::Fragment) @@ -640,6 +586,8 @@ int VkLightmapper::GetRaytracePipelineIndex() index |= 2; if (lm_sunlight && mesh->SunColor != FVector3(0.0f, 0.0f, 0.0f)) index |= 4; + if (lm_bounce) + index |= 8; return index; } @@ -654,8 +602,10 @@ FString VkLightmapper::LoadPrivateShaderLump(const char* lumpname) { "shaders/lightmap/frag_copy.glsl", frag_copy_glsl }, { "shaders/lightmap/frag_raytrace.glsl", frag_raytrace_glsl }, { "shaders/lightmap/frag_resolve.glsl", frag_resolve_glsl }, + { "shaders/lightmap/montecarlo.glsl", montecarlo_glsl }, { "shaders/lightmap/polyfill_rayquery.glsl", polyfill_rayquery_glsl }, { "shaders/lightmap/trace_ambient_occlusion.glsl", trace_ambient_occlusion_glsl }, + { "shaders/lightmap/trace_bounce.glsl", trace_bounce_glsl }, { "shaders/lightmap/trace_levelmesh.glsl", trace_levelmesh_glsl }, { "shaders/lightmap/trace_light.glsl", trace_light_glsl }, { "shaders/lightmap/trace_sunlight.glsl", trace_sunlight_glsl }, @@ -710,9 +660,8 @@ void VkLightmapper::CreateRaytracePipeline() .AddBinding(2, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT) .AddBinding(3, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT) .AddBinding(4, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT) -#ifdef USE_DRAWINDIRECT - .AddBinding(5, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT) -#endif + .AddBinding(5, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT) + .AddBinding(6, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT) .DebugName("raytrace.descriptorSetLayout0") .Create(fb->GetDevice()); @@ -739,9 +688,6 @@ void VkLightmapper::CreateRaytracePipeline() .AddSetLayout(raytrace.descriptorSetLayout0.get()) .AddSetLayout(raytrace.descriptorSetLayout1.get()) .AddSetLayout(fb->GetDescriptorSetManager()->GetBindlessLayout()) -#ifndef USE_DRAWINDIRECT - .AddPushConstantRange(VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(LightmapRaytracePC)) -#endif .DebugName("raytrace.pipelineLayout") .Create(fb->GetDevice()); @@ -763,7 +709,7 @@ void VkLightmapper::CreateRaytracePipeline() .DebugName("raytrace.renderPass") .Create(fb->GetDevice()); - for (int i = 0; i < 8; i++) + for (int i = 0; i < 16; i++) { raytrace.pipeline[i] = GraphicsPipelineBuilder() .Layout(raytrace.pipelineLayout.get()) @@ -783,7 +729,7 @@ void VkLightmapper::CreateRaytracePipeline() raytrace.descriptorPool0 = DescriptorPoolBuilder() .AddPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1) - .AddPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 5) + .AddPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 6) .MaxSets(1) .DebugName("raytrace.descriptorPool0") .Create(fb->GetDevice()); @@ -836,11 +782,10 @@ void VkLightmapper::UpdateAccelStructDescriptors() .AddBuffer(raytrace.descriptorSet0.get(), 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, uniforms.Buffer.get(), 0, sizeof(Uniforms)) .AddBuffer(raytrace.descriptorSet0.get(), 1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, fb->GetLevelMesh()->GetSurfaceIndexBuffer()) .AddBuffer(raytrace.descriptorSet0.get(), 2, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, fb->GetLevelMesh()->GetSurfaceBuffer()) - .AddBuffer(raytrace.descriptorSet0.get(), 3, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, lights.Buffer.get()) - .AddBuffer(raytrace.descriptorSet0.get(), 4, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, fb->GetLevelMesh()->GetPortalBuffer()) -#ifdef USE_DRAWINDIRECT - .AddBuffer(raytrace.descriptorSet0.get(), 5, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, drawindexed.ConstantsBuffer.get(), 0, drawindexed.BufferSize * sizeof(LightmapRaytracePC)) -#endif + .AddBuffer(raytrace.descriptorSet0.get(), 3, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, fb->GetLevelMesh()->GetLightBuffer()) + .AddBuffer(raytrace.descriptorSet0.get(), 4, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, fb->GetLevelMesh()->GetLightIndexBuffer()) + .AddBuffer(raytrace.descriptorSet0.get(), 5, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, fb->GetLevelMesh()->GetPortalBuffer()) + .AddBuffer(raytrace.descriptorSet0.get(), 6, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, drawindexed.ConstantsBuffer.get(), 0, drawindexed.BufferSize * sizeof(LightmapRaytracePC)) .Execute(fb->GetDevice()); } @@ -949,6 +894,9 @@ void VkLightmapper::CreateBlurPipeline() .Create(fb->GetDevice()); blur.sampler = SamplerBuilder() + .MinFilter(VK_FILTER_NEAREST) + .MagFilter(VK_FILTER_NEAREST) + .MipmapMode(VK_SAMPLER_MIPMAP_MODE_NEAREST) .DebugName("blur.Sampler") .Create(fb->GetDevice()); } @@ -1112,25 +1060,6 @@ void VkLightmapper::CreateUniformBuffer() .Create(fb->GetDevice()); } -void VkLightmapper::CreateLightBuffer() -{ - size_t size = sizeof(LightInfo) * lights.BufferSize; - - lights.Buffer = BufferBuilder() - .Usage( - VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, - VMA_MEMORY_USAGE_UNKNOWN, VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT) - .MemoryType( - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) - .Size(size) - .DebugName("LightmapLightBuffer") - .Create(fb->GetDevice()); - - lights.Lights = (LightInfo*)lights.Buffer->Map(0, size); - lights.Pos = 0; -} - void VkLightmapper::CreateTileBuffer() { size_t size = sizeof(CopyTileInfo) * copytiles.BufferSize; diff --git a/src/lightmapper/vk_lightmapper.h b/src/lightmapper/vk_lightmapper.h index 02f2b0f..b3c19ca 100644 --- a/src/lightmapper/vk_lightmapper.h +++ b/src/lightmapper/vk_lightmapper.h @@ -20,16 +20,16 @@ struct Uniforms struct LightmapRaytracePC { - uint32_t LightStart; - uint32_t LightEnd; int32_t SurfaceIndex; - int32_t PushPadding1; + int32_t Padding0; + int32_t Padding1; + int32_t Padding2; FVector3 WorldToLocal; float TextureSize; FVector3 ProjLocalToU; - float PushPadding2; + float Padding3; FVector3 ProjLocalToV; - float PushPadding3; + float Padding4; float TileX; float TileY; float TileWidth; @@ -79,22 +79,6 @@ struct LightmapBakeImage uint16_t maxY = 0; }; -struct LightInfo -{ - FVector3 Origin; - float Padding0; - FVector3 RelativeOrigin; - float Padding1; - float Radius; - float Intensity; - float InnerAngleCos; - float OuterAngleCos; - FVector3 SpotDir; - float Padding2; - FVector3 Color; - float Padding3; -}; - struct SelectedTile { LightmapTile* Tile = nullptr; @@ -103,8 +87,6 @@ struct SelectedTile bool Rendered = false; }; -static_assert(sizeof(LightInfo) == sizeof(float) * 20); - struct CopyTileInfo { int SrcPosX; @@ -147,7 +129,6 @@ private: void CreateBlurPipeline(); void CreateCopyPipeline(); void CreateUniformBuffer(); - void CreateLightBuffer(); void CreateTileBuffer(); void CreateDrawIndexedBuffer(); void CreateBakeImage(); @@ -180,15 +161,6 @@ private: VkDeviceSize StructStride = sizeof(Uniforms); } uniforms; - struct - { - const int BufferSize = 2 * 1024 * 1024; - std::unique_ptr Buffer; - LightInfo* Lights = nullptr; - int Pos = 0; - int ResetCounter = 0; - } lights; - struct { const int BufferSize = 100'000; @@ -211,7 +183,7 @@ private: std::unique_ptr vertRaytrace; std::unique_ptr vertScreenquad; std::unique_ptr vertCopy; - std::unique_ptr fragRaytrace[8]; + std::unique_ptr fragRaytrace[16]; std::unique_ptr fragResolve; std::unique_ptr fragBlur[2]; std::unique_ptr fragCopy; @@ -222,7 +194,7 @@ private: std::unique_ptr descriptorSetLayout0; std::unique_ptr descriptorSetLayout1; std::unique_ptr pipelineLayout; - std::unique_ptr pipeline[8]; + std::unique_ptr pipeline[16]; std::unique_ptr renderPass; std::unique_ptr descriptorPool0; std::unique_ptr descriptorPool1; diff --git a/src/lightmapper/vk_renderdevice.h b/src/lightmapper/vk_renderdevice.h index 60c0e74..080d3bc 100644 --- a/src/lightmapper/vk_renderdevice.h +++ b/src/lightmapper/vk_renderdevice.h @@ -25,7 +25,9 @@ public: VkLevelMesh* GetLevelMesh() { return levelmesh.get(); } VkLightmapper* GetLightmapper() { return lightmapper.get(); } - int GetBindlessTextureIndex(FTextureID texture) { return -1; } + int GetBindlessTextureIndex(FTextureID texture) { return 0; } + + bool IsRayQueryEnabled() const { return useRayQuery; } bool useRayQuery = false;