diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f169b3..8eaefd2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -174,6 +174,8 @@ set( SOURCES src/lightmap/vulkanbuilders.h src/lightmap/stacktrace.cpp src/lightmap/stacktrace.h + src/lightmap/surfaceclip.cpp + src/lightmap/surfaceclip.h src/lightmap/gpuraytracer.cpp src/lightmap/gpuraytracer.h src/lightmap/gpuraytracer2.cpp diff --git a/src/lightmap/cpuraytracer.cpp b/src/lightmap/cpuraytracer.cpp index 134b59d..6c3b2e4 100644 --- a/src/lightmap/cpuraytracer.cpp +++ b/src/lightmap/cpuraytracer.cpp @@ -6,7 +6,7 @@ #include "framework/binfile.h" #include "framework/templates.h" #include "framework/halffloat.h" -#include "delauneytriangulator.h" +#include "surfaceclip.h" #include #include #include @@ -347,14 +347,6 @@ float CPURaytracer::RadicalInverse_VdC(uint32_t bits) return float(bits) * 2.3283064365386963e-10f; // / 0x100000000 } -typedef DelauneyTriangulator::Vertex DTVertex; - -inline bool PointOnSide(const vec2& p, const DTVertex& v1, const DTVertex& v2, float tolerance) -{ - vec2 p2 = p - normalize(vec2(-(v2.y - v1.y), v2.x - v1.x)) * tolerance; - return (p2.y - v1.y) * (v2.x - v1.x) + (v1.x - p2.x) * (v2.y - v1.y) <= 0; -} - void CPURaytracer::CreateTasks(std::vector& tasks) { for (size_t i = 0; i < mesh->lightProbes.size(); i++) @@ -371,82 +363,24 @@ void CPURaytracer::CreateTasks(std::vector& tasks) for (size_t i = 0; i < mesh->surfaces.size(); i++) { if (i % 4096 == 0) - printf("\rGathering surface trace tasks: %d / %d", i, mesh->surfaces.size()); + printf("\rGathering surface trace tasks: %llu / %llu", i, mesh->surfaces.size()); Surface* surface = mesh->surfaces[i].get(); - int sampleWidth = surface->lightmapDims[0]; - int sampleHeight = surface->lightmapDims[1]; if (!surface->bSky) { - // Transformation matrix - mat3 base; - base[0] = surface->lightmapSteps[0].x; - base[1] = surface->lightmapSteps[0].y; - base[2] = surface->lightmapSteps[0].z; - base[3] = surface->lightmapSteps[1].x; - base[4] = surface->lightmapSteps[1].y; - base[5] = surface->lightmapSteps[1].z; - base[6] = surface->plane.a; - base[7] = surface->plane.b; - base[8] = surface->plane.c; - - mat3 inverseProjection = mat3::inverse(base); - - // Transform vertices to XY and triangulate - DelauneyTriangulator triangulator; - - BBox bounds; - - for (const auto& vertex : surface->verts) - { - auto flattenedVertex = inverseProjection * vertex; - - triangulator.vertices.emplace_back(flattenedVertex.x, flattenedVertex.y, nullptr); - - if (triangulator.vertices.empty()) - { - bounds = BBox(flattenedVertex, flattenedVertex); - } - else - { - bounds.AddPoint(flattenedVertex); - } - } - - triangulator.triangulate(); - - const float boundsWidth = bounds.max.x - bounds.min.x; - const float boundsHeight = bounds.max.y - bounds.min.y; - - const float offsetW = boundsWidth / sampleWidth; - const float offsetH = boundsHeight / sampleHeight; - - const float offset = (offsetH > offsetW ? offsetH : offsetW); - - auto isInBounds = [&](int x, int y) { - const float fx = (float(x) / float(sampleWidth)) * boundsWidth + bounds.min.x + offsetW; - const float fy = (float(y) / float(sampleHeight)) * boundsHeight + bounds.min.y + offsetH; - - for (const auto& triangle : triangulator.triangles) - { - if (PointOnSide(vec2(fx, fy), *triangle.A, *triangle.B, offset) - && PointOnSide(vec2(fx, fy), *triangle.B, *triangle.C, offset) - && PointOnSide(vec2(fx, fy), *triangle.C, *triangle.A, offset)) - { - return true; - } - } - return false; - }; + int sampleWidth = surface->lightmapDims[0]; + int sampleHeight = surface->lightmapDims[1]; fullTaskCount += size_t(sampleHeight) * size_t(sampleWidth); + SurfaceClip surfaceClip(surface); + for (int y = 0; y < sampleHeight; y++) { for (int x = 0; x < sampleWidth; x++) { - if (isInBounds(x, y)) + if (surfaceClip.SampleIsInBounds(float(x), float(y))) { CPUTraceTask task; task.id = (int)i; @@ -458,7 +392,7 @@ void CPURaytracer::CreateTasks(std::vector& tasks) } } } - printf("\rGathering surface trace tasks: %d / %d\n", mesh->surfaces.size(), mesh->surfaces.size()); + printf("\rGathering surface trace tasks: %llu / %llu\n", mesh->surfaces.size(), mesh->surfaces.size()); printf("\tDiscarded %.3f%% of all tasks\n", (1.0 - double(tasks.size()) / fullTaskCount) * 100.0); } diff --git a/src/lightmap/gpuraytracer.cpp b/src/lightmap/gpuraytracer.cpp index c215569..45b8d8d 100644 --- a/src/lightmap/gpuraytracer.cpp +++ b/src/lightmap/gpuraytracer.cpp @@ -7,6 +7,7 @@ #include "framework/templates.h" #include "framework/halffloat.h" #include "vulkanbuilders.h" +#include "surfaceclip.h" #include #include #include @@ -50,32 +51,7 @@ void GPURaytracer::Raytrace(LevelMesh* level) CreateVulkanObjects(); std::vector tasks; - for (size_t i = 0; i < mesh->lightProbes.size(); i++) - { - TraceTask task; - task.id = -(int)(i + 2); - task.x = 0; - task.y = 0; - tasks.push_back(task); - } - - for (size_t i = 0; i < mesh->surfaces.size(); i++) - { - Surface* surface = mesh->surfaces[i].get(); - int sampleWidth = surface->lightmapDims[0]; - int sampleHeight = surface->lightmapDims[1]; - for (int y = 0; y < sampleHeight; y++) - { - for (int x = 0; x < sampleWidth; x++) - { - TraceTask task; - task.id = (int)i; - task.x = x; - task.y = y; - tasks.push_back(task); - } - } - } + CreateTasks(tasks); std::vector HemisphereVectors; HemisphereVectors.reserve(bounceSampleCount); @@ -152,6 +128,57 @@ void GPURaytracer::Raytrace(LevelMesh* level) printf("Ray trace complete\n"); } +void GPURaytracer::CreateTasks(std::vector& tasks) +{ + tasks.resize(mesh->lightProbes.size()); + + for (size_t i = 0; i < mesh->lightProbes.size(); i++) + { + TraceTask task; + task.id = -(int)(i + 2); + task.x = 0; + task.y = 0; + tasks.push_back(task); + } + + size_t fullTaskCount = mesh->lightProbes.size(); + + for (size_t i = 0; i < mesh->surfaces.size(); i++) + { + if (i % 4096 == 0) + printf("\rGathering surface trace tasks: %llu / %llu", i, mesh->surfaces.size()); + + Surface* surface = mesh->surfaces[i].get(); + + if (!surface->bSky) + { + int sampleWidth = surface->lightmapDims[0]; + int sampleHeight = surface->lightmapDims[1]; + + fullTaskCount += size_t(sampleHeight) * size_t(sampleWidth); + + SurfaceClip surfaceClip(surface); + + for (int y = 0; y < sampleHeight; y++) + { + for (int x = 0; x < sampleWidth; x++) + { + if (surfaceClip.SampleIsInBounds(float(x), float(y))) + { + TraceTask task; + task.id = (int)i; + task.x = x; + task.y = y; + tasks.push_back(task); + } + } + } + } + } + printf("\rGathering surface trace tasks: %llu / %llu\n", mesh->surfaces.size(), mesh->surfaces.size()); + printf("\tDiscarded %.3f%% of all tasks\n", (1.0 - double(tasks.size()) / fullTaskCount) * 100.0); +} + void GPURaytracer::CreateVulkanObjects() { cmdpool = std::make_unique(device.get(), device->graphicsFamily); diff --git a/src/lightmap/gpuraytracer.h b/src/lightmap/gpuraytracer.h index c06f9f7..de81a4d 100644 --- a/src/lightmap/gpuraytracer.h +++ b/src/lightmap/gpuraytracer.h @@ -66,6 +66,7 @@ public: void Raytrace(LevelMesh* level); private: + void CreateTasks(std::vector& tasks); void CreateVulkanObjects(); void CreateVertexAndIndexBuffers(); void CreateBottomLevelAccelerationStructure(); diff --git a/src/lightmap/levelmesh.h b/src/lightmap/levelmesh.h index 3dce13c..57640cf 100644 --- a/src/lightmap/levelmesh.h +++ b/src/lightmap/levelmesh.h @@ -35,6 +35,7 @@ #include "framework/tarray.h" #include "framework/halffloat.h" #include "lightmaptexture.h" +#include "math/mathlib.h" struct MapSubsectorEx; struct IntSector; diff --git a/src/lightmap/surfaceclip.cpp b/src/lightmap/surfaceclip.cpp new file mode 100644 index 0000000..e343740 --- /dev/null +++ b/src/lightmap/surfaceclip.cpp @@ -0,0 +1,75 @@ +#include "surfaceclip.h" + +typedef DelauneyTriangulator::Vertex DTVertex; + +inline bool PointOnSide(const vec2& p, const DTVertex& v1, const DTVertex& v2, float tolerance) +{ + vec2 p2 = p - normalize(vec2(-(v2.y - v1.y), v2.x - v1.x)) * tolerance; + return (p2.y - v1.y) * (v2.x - v1.x) + (v1.x - p2.x) * (v2.y - v1.y) <= 0; +} + +SurfaceClip::SurfaceClip(Surface* surface) +{ + sampleWidth = float(surface->lightmapDims[0]); + sampleHeight = float(surface->lightmapDims[1]); + + // Transformation matrix + mat3 base; + base[0] = surface->lightmapSteps[0].x; + base[1] = surface->lightmapSteps[0].y; + base[2] = surface->lightmapSteps[0].z; + base[3] = surface->lightmapSteps[1].x; + base[4] = surface->lightmapSteps[1].y; + base[5] = surface->lightmapSteps[1].z; + base[6] = surface->plane.a; + base[7] = surface->plane.b; + base[8] = surface->plane.c; + + mat3 inverseProjection = mat3::inverse(base); + + // Transform vertices to XY and triangulate + triangulator.vertices.reserve(surface->verts.size()); + + for (const auto& vertex : surface->verts) + { + auto flattenedVertex = inverseProjection * vertex; + + triangulator.vertices.emplace_back(flattenedVertex.x, flattenedVertex.y, nullptr); + + if (triangulator.vertices.empty()) + { + bounds = BBox(flattenedVertex, flattenedVertex); + } + else + { + bounds.AddPoint(flattenedVertex); + } + } + + triangulator.triangulate(); + + // Init misc. variables + boundsWidth = bounds.max.x - bounds.min.x; + boundsHeight = bounds.max.y - bounds.min.y; + + offsetW = boundsWidth / sampleWidth; + offsetH = boundsHeight / sampleHeight; + + tolerance = (offsetH > offsetW ? offsetH : offsetW) * 2.0f; +} + +bool SurfaceClip::SampleIsInBounds(float x, float y) const +{ + const vec2 p = vec2((x / float(sampleWidth)) * boundsWidth + bounds.min.x + offsetW, (y / float(sampleHeight)) * boundsHeight + bounds.min.y + offsetH); + + for (const auto& triangle : triangulator.triangles) + { + if (PointOnSide(p, *triangle.A, *triangle.B, tolerance) + && PointOnSide(p, *triangle.B, *triangle.C, tolerance) + && PointOnSide(p, *triangle.C, *triangle.A, tolerance)) + { + return true; + } + } + return false; +} \ No newline at end of file diff --git a/src/lightmap/surfaceclip.h b/src/lightmap/surfaceclip.h new file mode 100644 index 0000000..96472b1 --- /dev/null +++ b/src/lightmap/surfaceclip.h @@ -0,0 +1,26 @@ +#pragma once + +#include "lightmap/levelmesh.h" +#include "math/mathlib.h" +#include "delauneytriangulator.h" + +class SurfaceClip +{ + DelauneyTriangulator triangulator; + + float sampleWidth; + float sampleHeight; + + BBox bounds; + float boundsWidth; + float boundsHeight; + float offsetW; + float offsetH; + float tolerance; + +public: + SurfaceClip(Surface* surface); + + // Tolerates points close enough to the surface to avoid missing used samples + bool SampleIsInBounds(float x, float y) const; +}; \ No newline at end of file