diff --git a/src/common/rendering/hwrenderer/data/hw_levelmesh.cpp b/src/common/rendering/hwrenderer/data/hw_levelmesh.cpp index d8f52f4e0..269b3c0e3 100644 --- a/src/common/rendering/hwrenderer/data/hw_levelmesh.cpp +++ b/src/common/rendering/hwrenderer/data/hw_levelmesh.cpp @@ -127,13 +127,13 @@ void LevelMesh::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; @@ -198,18 +198,14 @@ void LevelMesh::PackLightmapAtlas(int lightmapStartIndex) 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)); + // 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) { - 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; + 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; } diff --git a/src/common/rendering/hwrenderer/data/hw_lightmaptile.h b/src/common/rendering/hwrenderer/data/hw_lightmaptile.h index e7cbcc47d..dd41928f6 100644 --- a/src/common/rendering/hwrenderer/data/hw_lightmaptile.h +++ b/src/common/rendering/hwrenderer/data/hw_lightmaptile.h @@ -57,8 +57,8 @@ 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); } @@ -101,63 +101,66 @@ struct LightmapTile void SetupTileTransform(int textureSize) { - BBox bounds = Bounds; + // 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. - // round off dimensions - FVector3 roundedSize; - for (int i = 0; i < 3; i++) - { - bounds.min[i] = SampleDimension * (floor(bounds.min[i] / SampleDimension) - 1); - bounds.max[i] = SampleDimension * (ceil(bounds.max[i] / SampleDimension) + 1); - roundedSize[i] = (bounds.max[i] - bounds.min[i]) / SampleDimension; - } + 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) }; - - PlaneAxis axis = BestAxis(Plane); - - int width; - int height; - switch (axis) + int width, height; + switch (BestAxis(Plane)) { default: case AXIS_YZ: - width = (int)roundedSize.Y; - height = (int)roundedSize.Z; + 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)roundedSize.X; - height = (int)roundedSize.Z; + 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)roundedSize.X; - height = (int)roundedSize.Y; + 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; } - // clamp width - if (width > textureSize - 2) + textureSize -= 6; // Lightmapper needs some padding when baking + + // Tile can never be bigger than the texture. + if (width > textureSize) { - tCoords[0] *= ((float)(textureSize - 2) / (float)width); - width = (textureSize - 2); + tCoords[0] *= textureSize / (float)width; + width = textureSize; + } + if (height > textureSize) + { + tCoords[1] *= textureSize / (float)height; + height = textureSize; } - // clamp height - if (height > textureSize - 2) - { - tCoords[1] *= ((float)(textureSize - 2) / (float)height); - height = (textureSize - 2); - } + Transform.TranslateWorldToLocal.X = uvMin.X * SampleDimension; + Transform.TranslateWorldToLocal.Y = uvMin.Y * SampleDimension; + Transform.TranslateWorldToLocal.Z = uvMin.Z * SampleDimension; - Transform.TranslateWorldToLocal = bounds.min; Transform.ProjLocalToU = tCoords[0]; Transform.ProjLocalToV = tCoords[1]; diff --git a/src/common/rendering/vulkan/vk_lightmapper.cpp b/src/common/rendering/vulkan/vk_lightmapper.cpp index d82c55425..d6e3febd9 100644 --- a/src/common/rendering/vulkan/vk_lightmapper.cpp +++ b/src/common/rendering/vulkan/vk_lightmapper.cpp @@ -119,8 +119,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++) { @@ -130,21 +130,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() @@ -155,7 +159,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); @@ -878,6 +882,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()); } diff --git a/wadsrc/static/shaders/lightmap/frag_raytrace.glsl b/wadsrc/static/shaders/lightmap/frag_raytrace.glsl index dbb682152..6607b1142 100644 --- a/wadsrc/static/shaders/lightmap/frag_raytrace.glsl +++ b/wadsrc/static/shaders/lightmap/frag_raytrace.glsl @@ -21,7 +21,7 @@ void main() uint LightEnd = surfaces[SurfaceIndex].LightEnd; vec3 normal = surfaces[SurfaceIndex].Normal; - vec3 origin = worldpos + normal * 0.01; + vec3 origin = worldpos; #if defined(USE_SUNLIGHT) vec3 incoming = TraceSunLight(origin, normal); diff --git a/wadsrc/static/shaders/lightmap/vert_raytrace.glsl b/wadsrc/static/shaders/lightmap/vert_raytrace.glsl index 1713fff85..a872d31f7 100644 --- a/wadsrc/static/shaders/lightmap/vert_raytrace.glsl +++ b/wadsrc/static/shaders/lightmap/vert_raytrace.glsl @@ -29,9 +29,8 @@ 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; }