diff --git a/src/gl/dynlights/gl_lightbsp.cpp b/src/gl/dynlights/gl_lightbsp.cpp index 7cb25acd4..c7ae65fa1 100644 --- a/src/gl/dynlights/gl_lightbsp.cpp +++ b/src/gl/dynlights/gl_lightbsp.cpp @@ -1,6 +1,6 @@ // //--------------------------------------------------------------------------- -// Doom BSP tree on the GPU +// 2D collision tree for 1D shadowmap lights // Copyright(C) 2017 Magnus Norddahl // All rights reserved. // @@ -25,6 +25,7 @@ #include "gl/dynlights/gl_lightbsp.h" #include "gl/system/gl_interface.h" #include "r_state.h" +#include "g_levellocals.h" int FLightBSP::GetNodesBuffer() { @@ -32,10 +33,10 @@ int FLightBSP::GetNodesBuffer() return NodesBuffer; } -int FLightBSP::GetSegsBuffer() +int FLightBSP::GetLinesBuffer() { UpdateBuffers(); - return SegsBuffer; + return LinesBuffer; } void FLightBSP::UpdateBuffers() @@ -49,55 +50,19 @@ void FLightBSP::UpdateBuffers() void FLightBSP::GenerateBuffers() { + if (!Shape) + Shape.reset(new Level2DShape()); UploadNodes(); UploadSegs(); } void FLightBSP::UploadNodes() { - TArray gpunodes; - gpunodes.Resize(numnodes); - for (int i = 0; i < numnodes; i++) - { - const auto &node = nodes[i]; - auto &gpunode = gpunodes[i]; - - float a = -FIXED2FLOAT(node.dy); - float b = FIXED2FLOAT(node.dx); - float c = 0.0f; - float d = -(a * FIXED2FLOAT(node.x) + b * FIXED2FLOAT(node.y)); - - gpunode.plane[0] = a; - gpunode.plane[1] = b; - gpunode.plane[2] = c; - gpunode.plane[3] = d; - - for (int j = 0; j < 2; j++) - { - bool isNode = (!((size_t)node.children[j] & 1)); - if (isNode) - { - node_t *bsp = (node_t *)node.children[j]; - gpunode.children[j] = (int)(ptrdiff_t)(bsp - nodes); - gpunode.linecount[j] = -1; - } - else - { - subsector_t *sub = (subsector_t *)((BYTE *)node.children[j] - 1); - if (sub->numlines > 0) - gpunode.children[j] = (int)(ptrdiff_t)(sub->firstline - segs); - else - gpunode.children[j] = 0; - gpunode.linecount[j] = sub->numlines; - } - } - } - #if 0 - if (gpunodes.Size() > 0) + if (Shape->nodes.Size() > 0) { FILE *file = fopen("nodes.txt", "wb"); - fwrite(&gpunodes[0], sizeof(GPUNode) * gpunodes.Size(), 1, file); + fwrite(&Shape->nodes[0], sizeof(GPUNode) * Shape->nodes.Size(), 1, file); fclose(file); } #endif @@ -107,7 +72,7 @@ void FLightBSP::UploadNodes() glGenBuffers(1, (GLuint*)&NodesBuffer); glBindBuffer(GL_SHADER_STORAGE_BUFFER, NodesBuffer); - glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(GPUNode) * gpunodes.Size(), &gpunodes[0], GL_STATIC_DRAW); + glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(GPUNode) * Shape->nodes.Size(), &Shape->nodes[0], GL_STATIC_DRAW); glBindBuffer(GL_SHADER_STORAGE_BUFFER, oldBinding); NumNodes = numnodes; @@ -115,28 +80,24 @@ void FLightBSP::UploadNodes() void FLightBSP::UploadSegs() { - TArray gpusegs; - gpusegs.Resize(numsegs); - for (int i = 0; i < numsegs; i++) + TArray gpulines; + gpulines.Resize(level.lines.Size()); + for (unsigned int i = 0; i < level.lines.Size(); i++) { - const auto &seg = segs[i]; - auto &gpuseg = gpusegs[i]; + const auto &line = level.lines[i]; + auto &gpuseg = gpulines[i]; - gpuseg.x = (float)seg.v1->fX(); - gpuseg.y = (float)seg.v1->fY(); - gpuseg.dx = (float)seg.v2->fX() - gpuseg.x; - gpuseg.dy = (float)seg.v2->fY() - gpuseg.y; - gpuseg.bSolid = (seg.backsector == nullptr) ? 1.0f : 0.0f; - gpuseg.padding1 = 0.0f; - gpuseg.padding2 = 0.0f; - gpuseg.padding3 = 0.0f; + gpuseg.x = (float)line.v1->fX(); + gpuseg.y = (float)line.v1->fY(); + gpuseg.dx = (float)line.v2->fX() - gpuseg.x; + gpuseg.dy = (float)line.v2->fY() - gpuseg.y; } #if 0 - if (gpusegs.Size() > 0) + if (gpulines.Size() > 0) { - FILE *file = fopen("segs.txt", "wb"); - fwrite(&gpusegs[0], sizeof(GPUSeg) * gpusegs.Size(), 1, file); + FILE *file = fopen("lines.txt", "wb"); + fwrite(&gpulines[0], sizeof(GPULine) * gpulines.Size(), 1, file); fclose(file); } #endif @@ -144,9 +105,9 @@ void FLightBSP::UploadSegs() int oldBinding = 0; glGetIntegerv(GL_SHADER_STORAGE_BUFFER_BINDING, &oldBinding); - glGenBuffers(1, (GLuint*)&SegsBuffer); - glBindBuffer(GL_SHADER_STORAGE_BUFFER, SegsBuffer); - glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(GPUSeg) * gpusegs.Size(), &gpusegs[0], GL_STATIC_DRAW); + glGenBuffers(1, (GLuint*)&LinesBuffer); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, LinesBuffer); + glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(GPULine) * gpulines.Size(), &gpulines[0], GL_STATIC_DRAW); glBindBuffer(GL_SHADER_STORAGE_BUFFER, oldBinding); NumSegs = numsegs; @@ -159,9 +120,145 @@ void FLightBSP::Clear() glDeleteBuffers(1, (GLuint*)&NodesBuffer); NodesBuffer = 0; } - if (SegsBuffer != 0) + if (LinesBuffer != 0) { - glDeleteBuffers(1, (GLuint*)&SegsBuffer); - SegsBuffer = 0; + glDeleteBuffers(1, (GLuint*)&LinesBuffer); + LinesBuffer = 0; } + Shape.reset(); +} + +///////////////////////////////////////////////////////////////////////////// + +Level2DShape::Level2DShape() +{ + TArray lines; + TArray centroids; + for (unsigned int i = 0; i < level.lines.Size(); i++) + { + if (level.lines[i].backsector) + { + centroids.Push(FVector2(0.0f, 0.0f)); + continue; + } + + lines.Push(i); + + FVector2 v1 = { (float)level.lines[i].v1->fX(), (float)level.lines[i].v1->fY() }; + FVector2 v2 = { (float)level.lines[i].v2->fX(), (float)level.lines[i].v2->fY() }; + centroids.Push((v1 + v2) * 0.5f); + } + + TArray work_buffer; + work_buffer.Resize(lines.Size() * 2); + + root = subdivide(&lines[0], (int)lines.Size(), ¢roids[0], &work_buffer[0]); +} + +int Level2DShape::subdivide(int *lines, int num_lines, const FVector2 *centroids, int *work_buffer) +{ + if (num_lines == 0) + return -1; + + // Find bounding box and median of the lines + FVector2 median = FVector2(0.0f, 0.0f); + FVector2 aabb_min, aabb_max; + aabb_min.X = (float)level.lines[lines[0]].v1->fX(); + aabb_min.Y = (float)level.lines[lines[0]].v1->fY(); + aabb_max = aabb_min; + for (int i = 0; i < num_lines; i++) + { + float x1 = (float)level.lines[lines[i]].v1->fX(); + float y1 = (float)level.lines[lines[i]].v1->fY(); + float x2 = (float)level.lines[lines[i]].v2->fX(); + float y2 = (float)level.lines[lines[i]].v2->fY(); + + aabb_min.X = MIN(aabb_min.X, x1); + aabb_min.X = MIN(aabb_min.X, x2); + aabb_min.Y = MIN(aabb_min.Y, y1); + aabb_min.Y = MIN(aabb_min.Y, y2); + aabb_max.X = MAX(aabb_max.X, x1); + aabb_max.X = MAX(aabb_max.X, x2); + aabb_max.Y = MAX(aabb_max.Y, y1); + aabb_max.Y = MAX(aabb_max.Y, y2); + + median += centroids[lines[i]]; + } + median /= (float)num_lines; + + if (num_lines == 1) // Leaf node + { + nodes.Push(GPUNode(aabb_min, aabb_max, lines[0])); + return (int)nodes.Size() - 1; + } + + // Find the longest axis + float axis_lengths[2] = + { + aabb_max.X - aabb_min.X, + aabb_max.Y - aabb_min.Y + }; + + int axis_order[2] = { 0, 1 }; + FVector2 axis_plane[2] = { FVector2(1.0f, 0.0f), FVector2(0.0f, 1.0f) }; + std::sort(axis_order, axis_order + 2, [&](int a, int b) { return axis_lengths[a] > axis_lengths[b]; }); + + // Try split at longest axis, then if that fails the next longest, and then the remaining one + int left_count, right_count; + FVector2 axis; + for (int attempt = 0; attempt < 2; attempt++) + { + // Find the split plane for axis + FVector2 axis = axis_plane[axis_order[attempt]]; + FVector3 plane(axis, -(median | axis)); + + // Split lines into two + left_count = 0; + right_count = 0; + for (int i = 0; i < num_lines; i++) + { + int line_index = lines[i]; + + float side = FVector3(centroids[lines[i]], 1.0f) | plane; + if (side >= 0.0f) + { + work_buffer[left_count] = line_index; + left_count++; + } + else + { + work_buffer[num_lines + right_count] = line_index; + right_count++; + } + } + + if (left_count != 0 && right_count != 0) + break; + } + + // Check if something went wrong when splitting and do a random split instead + if (left_count == 0 || right_count == 0) + { + left_count = num_lines / 2; + right_count = num_lines - left_count; + } + else + { + // Move result back into lines list: + for (int i = 0; i < left_count; i++) + lines[i] = work_buffer[i]; + for (int i = 0; i < right_count; i++) + lines[i + left_count] = work_buffer[num_lines + i]; + } + + // Create child nodes: + int left_index = -1; + int right_index = -1; + if (left_count > 0) + left_index = subdivide(lines, left_count, centroids, work_buffer); + if (right_count > 0) + right_index = subdivide(lines + left_count, right_count, centroids, work_buffer); + + nodes.Push(GPUNode(aabb_min, aabb_max, left_index, right_index)); + return (int)nodes.Size() - 1; } diff --git a/src/gl/dynlights/gl_lightbsp.h b/src/gl/dynlights/gl_lightbsp.h index 42cc6fee2..c6f2b826d 100644 --- a/src/gl/dynlights/gl_lightbsp.h +++ b/src/gl/dynlights/gl_lightbsp.h @@ -1,19 +1,37 @@ #pragma once +#include + struct GPUNode { - float plane[4]; - int children[2]; - int linecount[2]; + GPUNode(const FVector2 &aabb_min, const FVector2 &aabb_max, int line_index) : aabb_left(aabb_min.X), aabb_top(aabb_min.Y), aabb_right(aabb_max.X), aabb_bottom(aabb_max.Y), left(-1), right(-1), line_index(line_index) { } + GPUNode(const FVector2 &aabb_min, const FVector2 &aabb_max, int left, int right) : aabb_left(aabb_min.X), aabb_top(aabb_min.Y), aabb_right(aabb_max.X), aabb_bottom(aabb_max.Y), left(left), right(right), line_index(-1) { } + + float aabb_left, aabb_top; + float aabb_right, aabb_bottom; + int left; + int right; + int line_index; + int padding; }; -struct GPUSeg +struct GPULine { float x, y; float dx, dy; - float bSolid; - float padding1, padding2, padding3; +}; + +class Level2DShape +{ +public: + Level2DShape(); + + TArray nodes; + int root; + +private: + int subdivide(int *lines, int num_lines, const FVector2 *centroids, int *work_buffer); }; class FLightBSP @@ -23,7 +41,7 @@ public: ~FLightBSP() { Clear(); } int GetNodesBuffer(); - int GetSegsBuffer(); + int GetLinesBuffer(); void Clear(); private: @@ -36,7 +54,9 @@ private: FLightBSP &operator=(FLightBSP &) = delete; int NodesBuffer = 0; - int SegsBuffer = 0; + int LinesBuffer = 0; int NumNodes = 0; int NumSegs = 0; + + std::unique_ptr Shape; }; diff --git a/src/gl/dynlights/gl_shadowmap.cpp b/src/gl/dynlights/gl_shadowmap.cpp index 005f38ad8..384b616b2 100644 --- a/src/gl/dynlights/gl_shadowmap.cpp +++ b/src/gl/dynlights/gl_shadowmap.cpp @@ -55,7 +55,7 @@ void FShadowMap::Update() GLRenderer->mShadowMapShader->Bind(); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, mLightList); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, mLightBSP.GetNodesBuffer()); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, mLightBSP.GetSegsBuffer()); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, mLightBSP.GetLinesBuffer()); glViewport(0, 0, 1024, 1024); GLRenderer->RenderScreenQuad(); diff --git a/src/gl/renderer/gl_renderbuffers.cpp b/src/gl/renderer/gl_renderbuffers.cpp index 67b0a0759..88814997c 100644 --- a/src/gl/renderer/gl_renderbuffers.cpp +++ b/src/gl/renderer/gl_renderbuffers.cpp @@ -772,6 +772,11 @@ void FGLRenderBuffers::CreateShadowMap() glGetIntegerv(GL_FRAMEBUFFER_BINDING, &frameBufferBinding); mShadowMapTexture = Create2DTexture("ShadowMap", GL_R32F, 1024, 1024); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + mShadowMapFB = CreateFrameBuffer("ShadowMapFB", mShadowMapTexture); glBindTexture(GL_TEXTURE_2D, textureBinding); diff --git a/wadsrc/static/shaders/glsl/main.fp b/wadsrc/static/shaders/glsl/main.fp index e986f65f0..07688d9df 100644 --- a/wadsrc/static/shaders/glsl/main.fp +++ b/wadsrc/static/shaders/glsl/main.fp @@ -158,7 +158,7 @@ float shadowmapAttenuation(vec4 lightpos, float shadowIndex) else u = dir.x / dir.y * 0.125 + (0.50 + 0.125); } - dir -= sign(dir); // margin, to remove wall acne + dir -= sign(dir) * 5.0; // margin, to remove wall acne float dist2 = dot(dir, dir); return texture(ShadowMap, vec2(u, v)).x > dist2 ? 1.0 : 0.0; } diff --git a/wadsrc/static/shaders/glsl/shadowmap.fp b/wadsrc/static/shaders/glsl/shadowmap.fp index 957e08b77..194da954e 100644 --- a/wadsrc/static/shaders/glsl/shadowmap.fp +++ b/wadsrc/static/shaders/glsl/shadowmap.fp @@ -4,26 +4,28 @@ out vec4 FragColor; struct GPUNode { - vec4 plane; - int children[2]; - int linecount[2]; + vec2 aabb_min; + vec2 aabb_max; + int left; + int right; + int line_index; + int padding; }; -struct GPUSeg +struct GPULine { vec2 pos; vec2 delta; - vec4 bSolid; }; layout(std430, binding = 2) buffer LightNodes { - GPUNode bspNodes[]; + GPUNode nodes[]; }; -layout(std430, binding = 3) buffer LightSegs +layout(std430, binding = 3) buffer LightLines { - GPUSeg bspSegs[]; + GPULine lines[]; }; layout(std430, binding = 4) buffer LightList @@ -31,87 +33,100 @@ layout(std430, binding = 4) buffer LightList vec4 lights[]; }; -//=========================================================================== -// -// Ray/BSP collision test. Returns where the ray hit something. -// -//=========================================================================== - -vec2 rayTest(vec2 from, vec2 to) +bool overlapRayAABB(vec2 ray_start2d, vec2 ray_end2d, vec2 aabb_min2d, vec2 aabb_max2d) +{ + // To do: simplify test to use a 2D test + vec3 ray_start = vec3(ray_start2d, 0.0); + vec3 ray_end = vec3(ray_end2d, 0.0); + vec3 aabb_min = vec3(aabb_min2d, -1.0); + vec3 aabb_max = vec3(aabb_max2d, 1.0); + + vec3 c = (ray_start + ray_end) * 0.5f; + vec3 w = ray_end - c; + vec3 h = (aabb_max - aabb_min) * 0.5f; // aabb.extents(); + + c -= (aabb_max + aabb_min) * 0.5f; // aabb.center(); + + vec3 v = abs(w); + + if (abs(c.x) > v.x + h.x || abs(c.y) > v.y + h.y || abs(c.z) > v.z + h.z) + return false; // disjoint; + + if (abs(c.y * w.z - c.z * w.y) > h.y * v.z + h.z * v.y || + abs(c.x * w.z - c.z * w.x) > h.x * v.z + h.z * v.x || + abs(c.x * w.y - c.y * w.x) > h.x * v.y + h.y * v.x) + return false; // disjoint; + + return true; // overlap; +} + +float intersectRayLine(vec2 ray_start, vec2 ray_end, int line_index, vec2 raydelta, float rayd, float raydist2) { - const int max_iterations = 50; const float epsilon = 0.0000001; + GPULine line = lines[line_index]; - // Avoid wall acne by adding some margin - vec2 margin = normalize(to - from); - - vec2 raydelta = to - from; - float raydist2 = dot(raydelta, raydelta); vec2 raynormal = vec2(raydelta.y, -raydelta.x); - float rayd = dot(raynormal, from); - if (raydist2 < 1.0 || bspNodes.length() == 0) - return to; - - int nodeIndex = bspNodes.length() - 1; - - for (int iteration = 0; iteration < max_iterations; iteration++) + float den = dot(raynormal, line.delta); + if (abs(den) > epsilon) { - GPUNode node = bspNodes[nodeIndex]; - int side = (dot(node.plane, vec4(from, 0.0, 1.0)) > 0.0) ? 1 : 0; - int linecount = node.linecount[side]; - if (linecount < 0) + float t_line = (rayd - dot(raynormal, line.pos)) / den; + if (t_line >= 0.0 && t_line <= 1.0) { - nodeIndex = node.children[side]; - } - else - { - int startLineIndex = node.children[side]; - - // Ray/line test each line segment. - bool hit_line = false; - for (int i = 0; i < linecount; i++) - { - GPUSeg seg = bspSegs[startLineIndex + i]; - - float den = dot(raynormal, seg.delta); - if (abs(den) > epsilon) - { - float t_seg = (rayd - dot(raynormal, seg.pos)) / den; - if (t_seg >= 0.0 && t_seg <= 1.0) - { - vec2 seghitdelta = seg.pos + seg.delta * t_seg - from; - if (dot(raydelta, seghitdelta) > 0.0 && dot(seghitdelta, seghitdelta) < raydist2) // We hit a line segment. - { - if (seg.bSolid.x > 0.0) // segment line is one-sided - return from + seghitdelta; - - // We hit a two-sided segment line. Move to the other side and continue ray tracing. - from = from + seghitdelta + margin; - - raydelta = to - from; - raydist2 = dot(raydelta, raydelta); - raynormal = vec2(raydelta.y, -raydelta.x); - rayd = dot(raynormal, from); - - if (raydist2 < 1.0 || bspNodes.length() == 0) - return to; - - nodeIndex = bspNodes.length() - 1; - - hit_line = true; - break; - } - } - } - } - - if (!hit_line) - return to; + vec2 linehitdelta = line.pos + line.delta * t_line - ray_start; + float t = dot(raydelta, linehitdelta) / raydist2; + return t > 0.0 ? t : 1.0; } } - return to; + return 1.0; +} + +bool isLeaf(int node_index) +{ + return nodes[node_index].line_index != -1; +} + +float rayTest(vec2 ray_start, vec2 ray_end) +{ + vec2 raydelta = ray_end - ray_start; + float raydist2 = dot(raydelta, raydelta); + vec2 raynormal = vec2(raydelta.y, -raydelta.x); + float rayd = dot(raynormal, ray_start); + if (raydist2 < 1.0) + return 1.0; + + float t = 1.0; + + int stack[16]; + int stack_pos = 1; + stack[0] = nodes.length() - 1; + while (stack_pos > 0) + { + int node_index = stack[stack_pos - 1]; + + if (!overlapRayAABB(ray_start, ray_end, nodes[node_index].aabb_min, nodes[node_index].aabb_max)) + { + stack_pos--; + } + else if (isLeaf(node_index)) + { + t = min(intersectRayLine(ray_start, ray_end, nodes[node_index].line_index, raydelta, rayd, raydist2), t); + stack_pos--; + } + else if (stack_pos == 16) + { + stack_pos--; // stack overflow + } + else + { + stack[stack_pos - 1] = nodes[node_index].left; + stack[stack_pos] = nodes[node_index].right; + stack_pos++; + } + } + + return t; } void main() @@ -134,8 +149,8 @@ void main() } pixelpos = lightpos + pixelpos * radius; - vec2 hitpos = rayTest(lightpos, pixelpos); - vec2 delta = hitpos - lightpos; + float t = rayTest(lightpos, pixelpos); + vec2 delta = (pixelpos - lightpos) * t; float dist2 = dot(delta, delta); FragColor = vec4(dist2, 0.0, 0.0, 1.0);