From 58c7c3c902662a40ba03cfcb4824b5dae33fcf11 Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Wed, 1 Mar 2017 02:46:03 +0100 Subject: [PATCH 01/18] Upload BSP tree to the GPU --- src/CMakeLists.txt | 1 + src/gl/dynlights/gl_lightbsp.cpp | 167 +++++++++++++++++++++++++++++++ src/gl/dynlights/gl_lightbsp.h | 42 ++++++++ src/gl/renderer/gl_renderer.h | 3 + 4 files changed, 213 insertions(+) create mode 100644 src/gl/dynlights/gl_lightbsp.cpp create mode 100644 src/gl/dynlights/gl_lightbsp.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4ff73e32c..0918229d3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -939,6 +939,7 @@ set( FASTMATH_SOURCES gl/dynlights/gl_glow.cpp gl/dynlights/gl_dynlight1.cpp gl/dynlights/gl_lightbuffer.cpp + gl/dynlights/gl_lightbsp.cpp gl/shaders/gl_shader.cpp gl/shaders/gl_texshader.cpp gl/shaders/gl_shaderprogram.cpp diff --git a/src/gl/dynlights/gl_lightbsp.cpp b/src/gl/dynlights/gl_lightbsp.cpp new file mode 100644 index 000000000..7cb25acd4 --- /dev/null +++ b/src/gl/dynlights/gl_lightbsp.cpp @@ -0,0 +1,167 @@ +// +//--------------------------------------------------------------------------- +// Doom BSP tree on the GPU +// Copyright(C) 2017 Magnus Norddahl +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//-------------------------------------------------------------------------- +// + +#include "gl/system/gl_system.h" +#include "gl/shaders/gl_shader.h" +#include "gl/dynlights/gl_lightbsp.h" +#include "gl/system/gl_interface.h" +#include "r_state.h" + +int FLightBSP::GetNodesBuffer() +{ + UpdateBuffers(); + return NodesBuffer; +} + +int FLightBSP::GetSegsBuffer() +{ + UpdateBuffers(); + return SegsBuffer; +} + +void FLightBSP::UpdateBuffers() +{ + if (numnodes != NumNodes || numsegs != NumSegs) // To do: there is probably a better way to detect a map change than this.. + Clear(); + + if (NodesBuffer == 0) + GenerateBuffers(); +} + +void FLightBSP::GenerateBuffers() +{ + 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) + { + FILE *file = fopen("nodes.txt", "wb"); + fwrite(&gpunodes[0], sizeof(GPUNode) * gpunodes.Size(), 1, file); + fclose(file); + } +#endif + + int oldBinding = 0; + glGetIntegerv(GL_SHADER_STORAGE_BUFFER_BINDING, &oldBinding); + + glGenBuffers(1, (GLuint*)&NodesBuffer); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, NodesBuffer); + glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(GPUNode) * gpunodes.Size(), &gpunodes[0], GL_STATIC_DRAW); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, oldBinding); + + NumNodes = numnodes; +} + +void FLightBSP::UploadSegs() +{ + TArray gpusegs; + gpusegs.Resize(numsegs); + for (int i = 0; i < numsegs; i++) + { + const auto &seg = segs[i]; + auto &gpuseg = gpusegs[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; + } + +#if 0 + if (gpusegs.Size() > 0) + { + FILE *file = fopen("segs.txt", "wb"); + fwrite(&gpusegs[0], sizeof(GPUSeg) * gpusegs.Size(), 1, file); + fclose(file); + } +#endif + + 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); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, oldBinding); + + NumSegs = numsegs; +} + +void FLightBSP::Clear() +{ + if (NodesBuffer != 0) + { + glDeleteBuffers(1, (GLuint*)&NodesBuffer); + NodesBuffer = 0; + } + if (SegsBuffer != 0) + { + glDeleteBuffers(1, (GLuint*)&SegsBuffer); + SegsBuffer = 0; + } +} diff --git a/src/gl/dynlights/gl_lightbsp.h b/src/gl/dynlights/gl_lightbsp.h new file mode 100644 index 000000000..42cc6fee2 --- /dev/null +++ b/src/gl/dynlights/gl_lightbsp.h @@ -0,0 +1,42 @@ + +#pragma once + +struct GPUNode +{ + float plane[4]; + int children[2]; + int linecount[2]; +}; + +struct GPUSeg +{ + float x, y; + float dx, dy; + float bSolid; + float padding1, padding2, padding3; +}; + +class FLightBSP +{ +public: + FLightBSP() { } + ~FLightBSP() { Clear(); } + + int GetNodesBuffer(); + int GetSegsBuffer(); + void Clear(); + +private: + void UpdateBuffers(); + void GenerateBuffers(); + void UploadNodes(); + void UploadSegs(); + + FLightBSP(const FLightBSP &) = delete; + FLightBSP &operator=(FLightBSP &) = delete; + + int NodesBuffer = 0; + int SegsBuffer = 0; + int NumNodes = 0; + int NumSegs = 0; +}; diff --git a/src/gl/renderer/gl_renderer.h b/src/gl/renderer/gl_renderer.h index ed2c523f2..2ba1cbc4d 100644 --- a/src/gl/renderer/gl_renderer.h +++ b/src/gl/renderer/gl_renderer.h @@ -6,6 +6,7 @@ #include "vectors.h" #include "r_renderer.h" #include "gl/data/gl_matrix.h" +#include "gl/dynlights/gl_lightbsp.h" struct particle_t; class FCanvasTexture; @@ -123,6 +124,8 @@ public: FPresent3DColumnShader *mPresent3dColumnShader; FPresent3DRowShader *mPresent3dRowShader; + FLightBSP mLightBSP; + FTexture *gllight; FTexture *glpart2; FTexture *glpart; From 6363c6cf58aff9a1da4f6942a84c2a993ca3a9fa Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Wed, 1 Mar 2017 03:33:53 +0100 Subject: [PATCH 02/18] Add a shadowmap shader --- src/CMakeLists.txt | 1 + src/gl/renderer/gl_renderer.cpp | 4 + src/gl/renderer/gl_renderer.h | 2 + src/gl/shaders/gl_shadowmapshader.cpp | 45 +++++++++ src/gl/shaders/gl_shadowmapshader.h | 15 +++ wadsrc/static/shaders/glsl/shadowmap.fp | 116 ++++++++++++++++++++++++ 6 files changed, 183 insertions(+) create mode 100644 src/gl/shaders/gl_shadowmapshader.cpp create mode 100644 src/gl/shaders/gl_shadowmapshader.h create mode 100644 wadsrc/static/shaders/glsl/shadowmap.fp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0918229d3..e92cd0dd3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -952,6 +952,7 @@ set( FASTMATH_SOURCES gl/shaders/gl_tonemapshader.cpp gl/shaders/gl_lensshader.cpp gl/shaders/gl_fxaashader.cpp + gl/shaders/gl_shadowmapshader.cpp gl/system/gl_interface.cpp gl/system/gl_framebuffer.cpp gl/system/gl_debug.cpp diff --git a/src/gl/renderer/gl_renderer.cpp b/src/gl/renderer/gl_renderer.cpp index 6021411be..b68ef57f9 100644 --- a/src/gl/renderer/gl_renderer.cpp +++ b/src/gl/renderer/gl_renderer.cpp @@ -60,6 +60,7 @@ #include "gl/shaders/gl_fxaashader.h" #include "gl/shaders/gl_presentshader.h" #include "gl/shaders/gl_present3dRowshader.h" +#include "gl/shaders/gl_shadowmapshader.h" #include "gl/stereo3d/gl_stereo3d.h" #include "gl/textures/gl_texture.h" #include "gl/textures/gl_translate.h" @@ -125,6 +126,7 @@ FGLRenderer::FGLRenderer(OpenGLFrameBuffer *fb) mSSAOCombineShader = nullptr; mFXAAShader = nullptr; mFXAALumaShader = nullptr; + mShadowMapShader = nullptr; } void gl_LoadModels(); @@ -153,6 +155,7 @@ void FGLRenderer::Initialize(int width, int height) mPresent3dCheckerShader = new FPresent3DCheckerShader(); mPresent3dColumnShader = new FPresent3DColumnShader(); mPresent3dRowShader = new FPresent3DRowShader(); + mShadowMapShader = new FShadowMapShader(); m2DDrawer = new F2DDrawer; // needed for the core profile, because someone decided it was a good idea to remove the default VAO. @@ -223,6 +226,7 @@ FGLRenderer::~FGLRenderer() if (mTonemapPalette) delete mTonemapPalette; if (mColormapShader) delete mColormapShader; if (mLensShader) delete mLensShader; + if (mShadowMapShader) delete mShadowMapShader; delete mFXAAShader; delete mFXAALumaShader; } diff --git a/src/gl/renderer/gl_renderer.h b/src/gl/renderer/gl_renderer.h index 2ba1cbc4d..b655f6d60 100644 --- a/src/gl/renderer/gl_renderer.h +++ b/src/gl/renderer/gl_renderer.h @@ -41,6 +41,7 @@ class FPresent3DColumnShader; class FPresent3DRowShader; class F2DDrawer; class FHardwareTexture; +class FShadowMapShader; inline float DEG2RAD(float deg) { @@ -123,6 +124,7 @@ public: FPresent3DCheckerShader *mPresent3dCheckerShader; FPresent3DColumnShader *mPresent3dColumnShader; FPresent3DRowShader *mPresent3dRowShader; + FShadowMapShader *mShadowMapShader; FLightBSP mLightBSP; diff --git a/src/gl/shaders/gl_shadowmapshader.cpp b/src/gl/shaders/gl_shadowmapshader.cpp new file mode 100644 index 000000000..674d0a04f --- /dev/null +++ b/src/gl/shaders/gl_shadowmapshader.cpp @@ -0,0 +1,45 @@ +// +//--------------------------------------------------------------------------- +// +// Copyright(C) 2016 Magnus Norddahl +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//-------------------------------------------------------------------------- +// + +#include "gl/system/gl_system.h" +#include "files.h" +#include "m_swap.h" +#include "v_video.h" +#include "gl/gl_functions.h" +#include "vectors.h" +#include "gl/system/gl_interface.h" +#include "gl/system/gl_framebuffer.h" +#include "gl/system/gl_cvars.h" +#include "gl/shaders/gl_shadowmapshader.h" + +void FShadowMapShader::Bind() +{ + if (!mShader) + { + mShader.Compile(FShaderProgram::Vertex, "shaders/glsl/screenquad.vp", "", 430); + mShader.Compile(FShaderProgram::Fragment, "shaders/glsl/shadowmap.fp", "", 430); + mShader.SetFragDataLocation(0, "FragColor"); + mShader.Link("shaders/glsl/shadowmap"); + mShader.SetAttribLocation(0, "PositionInProjection"); + } + mShader.Bind(); +} diff --git a/src/gl/shaders/gl_shadowmapshader.h b/src/gl/shaders/gl_shadowmapshader.h new file mode 100644 index 000000000..7d01f9974 --- /dev/null +++ b/src/gl/shaders/gl_shadowmapshader.h @@ -0,0 +1,15 @@ +#ifndef __GL_SHADOWMAPSHADER_H +#define __GL_SHADOWMAPSHADER_H + +#include "gl_shaderprogram.h" + +class FShadowMapShader +{ +public: + void Bind(); + +private: + FShaderProgram mShader; +}; + +#endif \ No newline at end of file diff --git a/wadsrc/static/shaders/glsl/shadowmap.fp b/wadsrc/static/shaders/glsl/shadowmap.fp new file mode 100644 index 000000000..d29108fff --- /dev/null +++ b/wadsrc/static/shaders/glsl/shadowmap.fp @@ -0,0 +1,116 @@ + +in vec2 TexCoord; +out vec4 FragColor; + +struct GPUNode +{ + vec4 plane; + int children[2]; + int linecount[2]; +}; + +struct GPUSeg +{ + vec2 pos; + vec2 delta; + vec4 bSolid; +}; + +layout(std430, binding = 2) buffer LightNodes +{ + GPUNode bspNodes[]; +}; + +layout(std430, binding = 3) buffer LightSegs +{ + GPUSeg bspSegs[]; +}; + +//=========================================================================== +// +// Ray/BSP collision test. Returns 0 if the ray hit a line, 1 otherwise. +// +//=========================================================================== + +float rayTest(vec2 from, vec2 to) +{ + const int max_iterations = 50; + const float epsilon = 0.0000001; + + // Avoid wall acne by adding some margin + vec2 margin = normalize(to - from); + to -= margin; + + 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 1.0; + + int nodeIndex = bspNodes.length() - 1; + + for (int iteration = 0; iteration < max_iterations; iteration++) + { + 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) + { + 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 0.0; + + // 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 1.0; + + nodeIndex = bspNodes.length() - 1; + + hit_line = true; + break; + } + } + } + } + + if (!hit_line) + return 1.0; + } + } + + return 0.0; +} + +void main() +{ + FragColor = vec4(rayTest(vec2(0.0, 0.0), vec2(1.0, 1.0)); +} From 7a4b01471d7a07145f8c153e8ac1d20f3d997053 Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Wed, 1 Mar 2017 04:05:54 +0100 Subject: [PATCH 03/18] Add class updating and managing the shadow map texture --- src/CMakeLists.txt | 1 + src/gl/dynlights/gl_shadowmap.cpp | 65 +++++++++++++++++++++++++ src/gl/dynlights/gl_shadowmap.h | 20 ++++++++ src/gl/renderer/gl_renderer.h | 4 +- src/gl/scene/gl_scene.cpp | 2 + wadsrc/static/shaders/glsl/shadowmap.fp | 2 +- 6 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 src/gl/dynlights/gl_shadowmap.cpp create mode 100644 src/gl/dynlights/gl_shadowmap.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e92cd0dd3..2cd811b67 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -940,6 +940,7 @@ set( FASTMATH_SOURCES gl/dynlights/gl_dynlight1.cpp gl/dynlights/gl_lightbuffer.cpp gl/dynlights/gl_lightbsp.cpp + gl/dynlights/gl_shadowmap.cpp gl/shaders/gl_shader.cpp gl/shaders/gl_texshader.cpp gl/shaders/gl_shaderprogram.cpp diff --git a/src/gl/dynlights/gl_shadowmap.cpp b/src/gl/dynlights/gl_shadowmap.cpp new file mode 100644 index 000000000..9de8e3697 --- /dev/null +++ b/src/gl/dynlights/gl_shadowmap.cpp @@ -0,0 +1,65 @@ +// +//--------------------------------------------------------------------------- +// 1D dynamic shadow maps +// Copyright(C) 2017 Magnus Norddahl +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//-------------------------------------------------------------------------- +// + +#include "gl/system/gl_system.h" +#include "gl/shaders/gl_shader.h" +#include "gl/dynlights/gl_shadowmap.h" +#include "gl/dynlights/gl_dynlight.h" +#include "gl/system/gl_interface.h" +#include "gl/system/gl_debug.h" +#include "gl/renderer/gl_renderer.h" +#include "gl/renderer/gl_postprocessstate.h" +#include "gl/shaders/gl_shadowmapshader.h" +#include "r_state.h" + +void FShadowMap::Clear() +{ + mLightBSP.Clear(); +} + +void FShadowMap::Update() +{ + TThinkerIterator it(STAT_DLIGHT); + while (true) + { + ADynamicLight *light = it.Next(); + if (!light) break; + } + + FGLDebug::PushGroup("ShadowMap"); + FGLPostProcessState savedState; + + GLRenderer->mShadowMapShader->Bind(); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, mLightBSP.GetNodesBuffer()); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, mLightBSP.GetSegsBuffer()); + + glViewport(0, 0, 1024, 1024); + GLRenderer->RenderScreenQuad(); + + const auto &viewport = GLRenderer->mScreenViewport; + glViewport(viewport.left, viewport.top, viewport.width, viewport.height); + + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, 0); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, 0); + + FGLDebug::PopGroup(); +} diff --git a/src/gl/dynlights/gl_shadowmap.h b/src/gl/dynlights/gl_shadowmap.h new file mode 100644 index 000000000..86a97dce5 --- /dev/null +++ b/src/gl/dynlights/gl_shadowmap.h @@ -0,0 +1,20 @@ + +#pragma once + +#include "gl/dynlights/gl_lightbsp.h" + +class FShadowMap +{ +public: + FShadowMap() { } + ~FShadowMap() { Clear(); } + + void Clear(); + void Update(); + +private: + FLightBSP mLightBSP; + + FShadowMap(const FShadowMap &) = delete; + FShadowMap &operator=(FShadowMap &) = delete; +}; diff --git a/src/gl/renderer/gl_renderer.h b/src/gl/renderer/gl_renderer.h index b655f6d60..bac7e6515 100644 --- a/src/gl/renderer/gl_renderer.h +++ b/src/gl/renderer/gl_renderer.h @@ -6,7 +6,7 @@ #include "vectors.h" #include "r_renderer.h" #include "gl/data/gl_matrix.h" -#include "gl/dynlights/gl_lightbsp.h" +#include "gl/dynlights/gl_shadowmap.h" struct particle_t; class FCanvasTexture; @@ -126,7 +126,7 @@ public: FPresent3DRowShader *mPresent3dRowShader; FShadowMapShader *mShadowMapShader; - FLightBSP mLightBSP; + FShadowMap mShadowMap; FTexture *gllight; FTexture *glpart2; diff --git a/src/gl/scene/gl_scene.cpp b/src/gl/scene/gl_scene.cpp index b852e1380..c7e602e0c 100644 --- a/src/gl/scene/gl_scene.cpp +++ b/src/gl/scene/gl_scene.cpp @@ -950,6 +950,8 @@ void FGLRenderer::RenderView (player_t* player) TThinkerIterator it(STAT_DLIGHT); GLRenderer->mLightCount = ((it.Next()) != NULL); + GLRenderer->mShadowMap.Update(); + sector_t * viewsector = RenderViewpoint(player->camera, NULL, FieldOfView.Degrees, ratio, fovratio, true, true); All.Unclock(); diff --git a/wadsrc/static/shaders/glsl/shadowmap.fp b/wadsrc/static/shaders/glsl/shadowmap.fp index d29108fff..72171244b 100644 --- a/wadsrc/static/shaders/glsl/shadowmap.fp +++ b/wadsrc/static/shaders/glsl/shadowmap.fp @@ -112,5 +112,5 @@ float rayTest(vec2 from, vec2 to) void main() { - FragColor = vec4(rayTest(vec2(0.0, 0.0), vec2(1.0, 1.0)); + FragColor = vec4(rayTest(vec2(0.0, 0.0), vec2(1.0, 1.0))); } From 62c285f7b3cc83a1434ff39ee7aec49dfd38d343 Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Wed, 1 Mar 2017 17:17:33 +0100 Subject: [PATCH 04/18] Create a shadowmap texture and upload light list --- src/gl/dynlights/gl_shadowmap.cpp | 50 ++++++++++++++++++++++--- src/gl/dynlights/gl_shadowmap.h | 4 ++ src/gl/renderer/gl_renderbuffers.cpp | 38 +++++++++++++++++++ src/gl/renderer/gl_renderbuffers.h | 8 ++++ wadsrc/static/shaders/glsl/shadowmap.fp | 18 ++++++++- 5 files changed, 111 insertions(+), 7 deletions(-) diff --git a/src/gl/dynlights/gl_shadowmap.cpp b/src/gl/dynlights/gl_shadowmap.cpp index 9de8e3697..d0b6f7715 100644 --- a/src/gl/dynlights/gl_shadowmap.cpp +++ b/src/gl/dynlights/gl_shadowmap.cpp @@ -28,27 +28,32 @@ #include "gl/system/gl_debug.h" #include "gl/renderer/gl_renderer.h" #include "gl/renderer/gl_postprocessstate.h" +#include "gl/renderer/gl_renderbuffers.h" #include "gl/shaders/gl_shadowmapshader.h" #include "r_state.h" void FShadowMap::Clear() { + if (mLightList != 0) + { + glDeleteBuffers(1, (GLuint*)&mLightList); + mLightList = 0; + } + mLightBSP.Clear(); } void FShadowMap::Update() { - TThinkerIterator it(STAT_DLIGHT); - while (true) - { - ADynamicLight *light = it.Next(); - if (!light) break; - } + UploadLights(); FGLDebug::PushGroup("ShadowMap"); FGLPostProcessState savedState; + GLRenderer->mBuffers->BindShadowMapFB(); + GLRenderer->mShadowMapShader->Bind(); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, mLightList); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, mLightBSP.GetNodesBuffer()); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, mLightBSP.GetSegsBuffer()); @@ -58,8 +63,41 @@ void FShadowMap::Update() const auto &viewport = GLRenderer->mScreenViewport; glViewport(viewport.left, viewport.top, viewport.width, viewport.height); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, 0); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, 0); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, 0); FGLDebug::PopGroup(); } + +void FShadowMap::UploadLights() +{ + lights.Clear(); + + TThinkerIterator it(STAT_DLIGHT); + while (true) + { + ADynamicLight *light = it.Next(); + if (!light) break; + + lights.Push(light->X()); + lights.Push(light->Y()); + lights.Push(light->Z()); + lights.Push(light->GetRadius()); + + if (lights.Size() == 1024) // Only 1024 lights for now + break; + } + + while (lights.Size() < 1024 * 4) + lights.Push(0.0f); + + if (mLightList == 0) + glGenBuffers(1, (GLuint*)&mLightList); + + int oldBinding = 0; + glGetIntegerv(GL_SHADER_STORAGE_BUFFER_BINDING, &oldBinding); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, mLightList); + glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(float) * lights.Size(), &lights[0], GL_STATIC_DRAW); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, oldBinding); +} \ No newline at end of file diff --git a/src/gl/dynlights/gl_shadowmap.h b/src/gl/dynlights/gl_shadowmap.h index 86a97dce5..1289965f2 100644 --- a/src/gl/dynlights/gl_shadowmap.h +++ b/src/gl/dynlights/gl_shadowmap.h @@ -13,7 +13,11 @@ public: void Update(); private: + void UploadLights(); + FLightBSP mLightBSP; + int mLightList = 0; + TArray lights; FShadowMap(const FShadowMap &) = delete; FShadowMap &operator=(FShadowMap &) = delete; diff --git a/src/gl/renderer/gl_renderbuffers.cpp b/src/gl/renderer/gl_renderbuffers.cpp index fad79767c..67b0a0759 100644 --- a/src/gl/renderer/gl_renderbuffers.cpp +++ b/src/gl/renderer/gl_renderbuffers.cpp @@ -741,6 +741,44 @@ void FGLRenderBuffers::BindEyeFB(int eye, bool readBuffer) glBindFramebuffer(readBuffer ? GL_READ_FRAMEBUFFER : GL_FRAMEBUFFER, mEyeFBs[eye]); } +//========================================================================== +// +// Shadow map texture and frame buffers +// +//========================================================================== + +void FGLRenderBuffers::BindShadowMapFB() +{ + CreateShadowMap(); + glBindFramebuffer(GL_FRAMEBUFFER, mShadowMapFB); +} + +void FGLRenderBuffers::BindShadowMapTexture(int texunit) +{ + CreateShadowMap(); + glActiveTexture(GL_TEXTURE0 + texunit); + glBindTexture(GL_TEXTURE_2D, mShadowMapTexture); +} + +void FGLRenderBuffers::CreateShadowMap() +{ + if (mShadowMapTexture != 0) + return; + + GLint activeTex, textureBinding, frameBufferBinding; + glGetIntegerv(GL_ACTIVE_TEXTURE, &activeTex); + glActiveTexture(GL_TEXTURE0); + glGetIntegerv(GL_TEXTURE_BINDING_2D, &textureBinding); + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &frameBufferBinding); + + mShadowMapTexture = Create2DTexture("ShadowMap", GL_R32F, 1024, 1024); + mShadowMapFB = CreateFrameBuffer("ShadowMapFB", mShadowMapTexture); + + glBindTexture(GL_TEXTURE_2D, textureBinding); + glActiveTexture(activeTex); + glBindFramebuffer(GL_FRAMEBUFFER, frameBufferBinding); +} + //========================================================================== // // Makes the scene frame buffer active (multisample, depth, stecil, etc.) diff --git a/src/gl/renderer/gl_renderbuffers.h b/src/gl/renderer/gl_renderbuffers.h index f2f7b7cb9..5df3bcca0 100644 --- a/src/gl/renderer/gl_renderbuffers.h +++ b/src/gl/renderer/gl_renderbuffers.h @@ -49,6 +49,9 @@ public: void BindEyeTexture(int eye, int texunit); void BindEyeFB(int eye, bool readBuffer = false); + void BindShadowMapFB(); + void BindShadowMapTexture(int index); + enum { NumBloomLevels = 4 }; FGLBloomTextureLevel BloomLevels[NumBloomLevels]; @@ -89,6 +92,7 @@ private: void CreateBloom(int width, int height); void CreateExposureLevels(int width, int height); void CreateEyeBuffers(int eye); + void CreateShadowMap(); void CreateAmbientOcclusion(int width, int height); GLuint Create2DTexture(const FString &name, GLuint format, int width, int height, const void *data = nullptr); GLuint Create2DMultisampleTexture(const FString &name, GLuint format, int width, int height, int samples, bool fixedSampleLocations); @@ -133,6 +137,10 @@ private: TArray mEyeTextures; TArray mEyeFBs; + // Shadow map texture + GLuint mShadowMapTexture = 0; + GLuint mShadowMapFB = 0; + static bool FailedCreate; static bool BuffersActive; }; diff --git a/wadsrc/static/shaders/glsl/shadowmap.fp b/wadsrc/static/shaders/glsl/shadowmap.fp index 72171244b..ed530e2dd 100644 --- a/wadsrc/static/shaders/glsl/shadowmap.fp +++ b/wadsrc/static/shaders/glsl/shadowmap.fp @@ -16,6 +16,11 @@ struct GPUSeg vec4 bSolid; }; +layout(std430, binding = 1) buffer LightList +{ + vec4 lights[]; +}; + layout(std430, binding = 2) buffer LightNodes { GPUNode bspNodes[]; @@ -112,5 +117,16 @@ float rayTest(vec2 from, vec2 to) void main() { - FragColor = vec4(rayTest(vec2(0.0, 0.0), vec2(1.0, 1.0))); + int lightIndex = int(gl_FragCoord.y); + int x = int(gl_FragCoord.x); + + vec4 light = lights[lightIndex]; + float radius = light.w; + vec2 lightpos = light.xy; + vec2 pixelpos = lightpos + vec2(10.0); + + if (radius > 0.0) + FragColor = vec4(rayTest(lightpos, pixelpos), 0.0, 0.0, 1.0); + else + FragColor = vec4(1.0, 0.0, 0.0, 1.0); } From d450deee765cbf1525835b95b0e88dcc070594b9 Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Thu, 2 Mar 2017 16:08:15 +0100 Subject: [PATCH 05/18] Generate shadow map for lights --- wadsrc/static/shaders/glsl/shadowmap.fp | 37 +++++++++++++++++-------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/wadsrc/static/shaders/glsl/shadowmap.fp b/wadsrc/static/shaders/glsl/shadowmap.fp index ed530e2dd..9b5fc2782 100644 --- a/wadsrc/static/shaders/glsl/shadowmap.fp +++ b/wadsrc/static/shaders/glsl/shadowmap.fp @@ -33,18 +33,17 @@ layout(std430, binding = 3) buffer LightSegs //=========================================================================== // -// Ray/BSP collision test. Returns 0 if the ray hit a line, 1 otherwise. +// Ray/BSP collision test. Returns where the ray hit something. // //=========================================================================== -float rayTest(vec2 from, vec2 to) +vec2 rayTest(vec2 from, vec2 to) { const int max_iterations = 50; const float epsilon = 0.0000001; // Avoid wall acne by adding some margin vec2 margin = normalize(to - from); - to -= margin; vec2 raydelta = to - from; float raydist2 = dot(raydelta, raydelta); @@ -52,7 +51,7 @@ float rayTest(vec2 from, vec2 to) float rayd = dot(raynormal, from); if (raydist2 < 1.0 || bspNodes.length() == 0) - return 1.0; + return to; int nodeIndex = bspNodes.length() - 1; @@ -85,7 +84,7 @@ float rayTest(vec2 from, vec2 to) 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 0.0; + return from + seghitdelta; // We hit a two-sided segment line. Move to the other side and continue ray tracing. from = from + seghitdelta + margin; @@ -96,7 +95,7 @@ float rayTest(vec2 from, vec2 to) rayd = dot(raynormal, from); if (raydist2 < 1.0 || bspNodes.length() == 0) - return 1.0; + return to; nodeIndex = bspNodes.length() - 1; @@ -108,25 +107,41 @@ float rayTest(vec2 from, vec2 to) } if (!hit_line) - return 1.0; + return to; } } - return 0.0; + return to; } void main() { int lightIndex = int(gl_FragCoord.y); - int x = int(gl_FragCoord.x); vec4 light = lights[lightIndex]; float radius = light.w; vec2 lightpos = light.xy; - vec2 pixelpos = lightpos + vec2(10.0); if (radius > 0.0) - FragColor = vec4(rayTest(lightpos, pixelpos), 0.0, 0.0, 1.0); + { + vec2 pixelpos; + switch (int(gl_FragCoord.x) / 256) + { + case 0: pixelpos = vec2((gl_FragCoord.x - 128.0) / 128.0, 1.0); break; + case 1: pixelpos = vec2(1.0, (gl_FragCoord.x - 384.0) / 128.0); break; + case 2: pixelpos = vec2((gl_FragCoord.x - 640.0) / 128.0, -1.0); break; + case 3: pixelpos = vec2(-1.0, (gl_FragCoord.x - 896.0) / 128.0); break; + } + pixelpos = lightpos + pixelpos * radius; + + vec2 hitpos = rayTest(lightpos, pixelpos); + vec2 delta = hitpos - lightpos; + float dist2 = dot(delta, delta); + + FragColor = vec4(dist2, 0.0, 0.0, 1.0); + } else + { FragColor = vec4(1.0, 0.0, 0.0, 1.0); + } } From 538d516c9a2aea3014c10b9c76a9a25c17becdb5 Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Thu, 2 Mar 2017 18:07:47 +0100 Subject: [PATCH 06/18] Upload shadow map index for each light to main.fp Move storage buffer binding location --- src/gl/dynlights/gl_dynlight1.cpp | 6 +++++- src/gl/dynlights/gl_shadowmap.cpp | 25 ++++++++++++++----------- src/gl/dynlights/gl_shadowmap.h | 8 +++++++- wadsrc/static/shaders/glsl/main.fp | 2 +- wadsrc/static/shaders/glsl/shadowmap.fp | 10 +++++----- 5 files changed, 32 insertions(+), 19 deletions(-) diff --git a/src/gl/dynlights/gl_dynlight1.cpp b/src/gl/dynlights/gl_dynlight1.cpp index 2f8ef44ed..4037545aa 100644 --- a/src/gl/dynlights/gl_dynlight1.cpp +++ b/src/gl/dynlights/gl_dynlight1.cpp @@ -108,6 +108,10 @@ bool gl_GetLight(int group, Plane & p, ADynamicLight * light, bool checkside, FD i = 1; } + float shadowIndex = (float)GLRenderer->mShadowMap.ShadowMapIndex(light); + if (!!(light->flags4 & MF4_ATTENUATE)) // Store attenuate flag in the sign bit of the float + shadowIndex = -shadowIndex; + float *data = &ldata.arrays[i][ldata.arrays[i].Reserve(8)]; data[0] = pos.X; data[1] = pos.Z; @@ -116,7 +120,7 @@ bool gl_GetLight(int group, Plane & p, ADynamicLight * light, bool checkside, FD data[4] = r; data[5] = g; data[6] = b; - data[7] = !!(light->flags4 & MF4_ATTENUATE); + data[7] = shadowIndex; return true; } diff --git a/src/gl/dynlights/gl_shadowmap.cpp b/src/gl/dynlights/gl_shadowmap.cpp index d0b6f7715..b1c95ff03 100644 --- a/src/gl/dynlights/gl_shadowmap.cpp +++ b/src/gl/dynlights/gl_shadowmap.cpp @@ -53,7 +53,7 @@ void FShadowMap::Update() GLRenderer->mBuffers->BindShadowMapFB(); GLRenderer->mShadowMapShader->Bind(); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, mLightList); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, mLightList); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, mLightBSP.GetNodesBuffer()); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, mLightBSP.GetSegsBuffer()); @@ -63,7 +63,7 @@ void FShadowMap::Update() const auto &viewport = GLRenderer->mScreenViewport; glViewport(viewport.left, viewport.top, viewport.width, viewport.height); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, 0); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, 0); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, 0); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, 0); @@ -72,7 +72,8 @@ void FShadowMap::Update() void FShadowMap::UploadLights() { - lights.Clear(); + mLights.Clear(); + mLightToShadowmap.Clear(mLightToShadowmap.CountUsed() * 2); // To do: allow clearing a TMap while building up a reserve TThinkerIterator it(STAT_DLIGHT); while (true) @@ -80,17 +81,19 @@ void FShadowMap::UploadLights() ADynamicLight *light = it.Next(); if (!light) break; - lights.Push(light->X()); - lights.Push(light->Y()); - lights.Push(light->Z()); - lights.Push(light->GetRadius()); + mLightToShadowmap[light] = mLights.Size(); - if (lights.Size() == 1024) // Only 1024 lights for now + mLights.Push(light->X()); + mLights.Push(light->Y()); + mLights.Push(light->Z()); + mLights.Push(light->GetRadius()); + + if (mLights.Size() == 1024) // Only 1024 lights for now break; } - while (lights.Size() < 1024 * 4) - lights.Push(0.0f); + while (mLights.Size() < 1024 * 4) + mLights.Push(0.0f); if (mLightList == 0) glGenBuffers(1, (GLuint*)&mLightList); @@ -98,6 +101,6 @@ void FShadowMap::UploadLights() int oldBinding = 0; glGetIntegerv(GL_SHADER_STORAGE_BUFFER_BINDING, &oldBinding); glBindBuffer(GL_SHADER_STORAGE_BUFFER, mLightList); - glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(float) * lights.Size(), &lights[0], GL_STATIC_DRAW); + glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(float) * mLights.Size(), &mLights[0], GL_STATIC_DRAW); glBindBuffer(GL_SHADER_STORAGE_BUFFER, oldBinding); } \ No newline at end of file diff --git a/src/gl/dynlights/gl_shadowmap.h b/src/gl/dynlights/gl_shadowmap.h index 1289965f2..cc32ec777 100644 --- a/src/gl/dynlights/gl_shadowmap.h +++ b/src/gl/dynlights/gl_shadowmap.h @@ -2,6 +2,9 @@ #pragma once #include "gl/dynlights/gl_lightbsp.h" +#include "tarray.h" + +class ADynamicLight; class FShadowMap { @@ -12,12 +15,15 @@ public: void Clear(); void Update(); + int ShadowMapIndex(ADynamicLight *light) { return mLightToShadowmap[light]; } + private: void UploadLights(); FLightBSP mLightBSP; int mLightList = 0; - TArray lights; + TArray mLights; + TMap mLightToShadowmap; FShadowMap(const FShadowMap &) = delete; FShadowMap &operator=(FShadowMap &) = delete; diff --git a/wadsrc/static/shaders/glsl/main.fp b/wadsrc/static/shaders/glsl/main.fp index a6bc6bbd7..2e27e5770 100644 --- a/wadsrc/static/shaders/glsl/main.fp +++ b/wadsrc/static/shaders/glsl/main.fp @@ -154,7 +154,7 @@ float diffuseContribution(vec3 lightDirection, vec3 normal) float pointLightAttenuation(vec4 lightpos, float attenuate) { float attenuation = max(lightpos.w - distance(pixelpos.xyz, lightpos.xyz),0.0) / lightpos.w; - if (attenuate == 0.0) + if (attenuate >= 0.0) // Sign bit is the attenuate flag { return attenuation; } diff --git a/wadsrc/static/shaders/glsl/shadowmap.fp b/wadsrc/static/shaders/glsl/shadowmap.fp index 9b5fc2782..1c9b3a754 100644 --- a/wadsrc/static/shaders/glsl/shadowmap.fp +++ b/wadsrc/static/shaders/glsl/shadowmap.fp @@ -16,11 +16,6 @@ struct GPUSeg vec4 bSolid; }; -layout(std430, binding = 1) buffer LightList -{ - vec4 lights[]; -}; - layout(std430, binding = 2) buffer LightNodes { GPUNode bspNodes[]; @@ -31,6 +26,11 @@ layout(std430, binding = 3) buffer LightSegs GPUSeg bspSegs[]; }; +layout(std430, binding = 4) buffer LightList +{ + vec4 lights[]; +}; + //=========================================================================== // // Ray/BSP collision test. Returns where the ray hit something. From 0d1deddae57a4dcdb9eaa1e31e8405888a1357d6 Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Thu, 2 Mar 2017 19:10:57 +0100 Subject: [PATCH 07/18] Bind shadow map texture for main.fp and sample from the shadowmap texture --- src/gl/dynlights/gl_shadowmap.cpp | 2 ++ src/gl/shaders/gl_shader.cpp | 3 +++ wadsrc/static/shaders/glsl/main.fp | 34 ++++++++++++++++++++++++++++-- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/gl/dynlights/gl_shadowmap.cpp b/src/gl/dynlights/gl_shadowmap.cpp index b1c95ff03..b70304db0 100644 --- a/src/gl/dynlights/gl_shadowmap.cpp +++ b/src/gl/dynlights/gl_shadowmap.cpp @@ -67,6 +67,8 @@ void FShadowMap::Update() glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, 0); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, 0); + GLRenderer->mBuffers->BindShadowMapTexture(16); + FGLDebug::PopGroup(); } diff --git a/src/gl/shaders/gl_shader.cpp b/src/gl/shaders/gl_shader.cpp index 421139050..d36f4c4fa 100644 --- a/src/gl/shaders/gl_shader.cpp +++ b/src/gl/shaders/gl_shader.cpp @@ -270,6 +270,9 @@ bool FShader::Load(const char * name, const char * vert_prog_lump, const char * if (tempindex > 0) glUniform1i(tempindex, i - 1); } + int shadowmapindex = glGetUniformLocation(hShader, "ShadowMap"); + if (shadowmapindex > 0) glUniform1i(shadowmapindex, 16); + glUseProgram(0); return !!linked; } diff --git a/wadsrc/static/shaders/glsl/main.fp b/wadsrc/static/shaders/glsl/main.fp index 2e27e5770..0d4cf0607 100644 --- a/wadsrc/static/shaders/glsl/main.fp +++ b/wadsrc/static/shaders/glsl/main.fp @@ -26,6 +26,7 @@ out vec4 FragNormal; uniform sampler2D tex; +uniform sampler2D ShadowMap; vec4 Process(vec4 color); vec4 ProcessTexel(); @@ -132,6 +133,34 @@ float R_DoomLightingEquation(float light) return lightscale; } +//=========================================================================== +// +// Check if light is in shadow according to its 1D shadow map +// +//=========================================================================== + +float shadowmapAttenuation(vec4 lightpos, float shadowIndex) +{ + float u; + float v = (abs(shadowIndex) + 0.5) / 1024.0; + vec2 dir = (pixelpos.xz - lightpos.xz); + if (abs(dir.x) > abs(dir.y)) + { + if (dir.x >= 0.0) + u = dir.y / dir.x * 0.125 + (0.25 + 0.125); + else + u = dir.y / dir.x * 0.125 + (0.75 + 0.125); + } + else + { + if (dir.y >= 0.0) + u = dir.x / dir.y * 0.125 + 0.125; + else + u = dir.x / dir.y * 0.125 + (0.50 + 0.125); + } + return texture(ShadowMap, vec2(u, v)).x < dot(dir, dir) ? 1.0 : 0.0; +} + //=========================================================================== // // Standard lambertian diffuse light calculation @@ -151,10 +180,11 @@ float diffuseContribution(vec3 lightDirection, vec3 normal) // //=========================================================================== -float pointLightAttenuation(vec4 lightpos, float attenuate) +float pointLightAttenuation(vec4 lightpos, float shadowIndex) { float attenuation = max(lightpos.w - distance(pixelpos.xyz, lightpos.xyz),0.0) / lightpos.w; - if (attenuate >= 0.0) // Sign bit is the attenuate flag + attenuation *= shadowmapAttenuation(lightpos, shadowIndex); + if (shadowIndex >= 0.0) // Sign bit is the attenuated light flag { return attenuation; } From 8515f9720aa45246db1c6e0323c9070555037504 Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Sat, 4 Mar 2017 09:14:01 +0100 Subject: [PATCH 08/18] 1D shadow maps are now working --- src/gl/dynlights/gl_shadowmap.cpp | 2 +- wadsrc/static/shaders/glsl/main.fp | 4 +++- wadsrc/static/shaders/glsl/shadowmap.fp | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/gl/dynlights/gl_shadowmap.cpp b/src/gl/dynlights/gl_shadowmap.cpp index b70304db0..005f38ad8 100644 --- a/src/gl/dynlights/gl_shadowmap.cpp +++ b/src/gl/dynlights/gl_shadowmap.cpp @@ -83,7 +83,7 @@ void FShadowMap::UploadLights() ADynamicLight *light = it.Next(); if (!light) break; - mLightToShadowmap[light] = mLights.Size(); + mLightToShadowmap[light] = mLights.Size() / 4; mLights.Push(light->X()); mLights.Push(light->Y()); diff --git a/wadsrc/static/shaders/glsl/main.fp b/wadsrc/static/shaders/glsl/main.fp index 0d4cf0607..e986f65f0 100644 --- a/wadsrc/static/shaders/glsl/main.fp +++ b/wadsrc/static/shaders/glsl/main.fp @@ -158,7 +158,9 @@ float shadowmapAttenuation(vec4 lightpos, float shadowIndex) else u = dir.x / dir.y * 0.125 + (0.50 + 0.125); } - return texture(ShadowMap, vec2(u, v)).x < dot(dir, dir) ? 1.0 : 0.0; + dir -= sign(dir); // 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 1c9b3a754..957e08b77 100644 --- a/wadsrc/static/shaders/glsl/shadowmap.fp +++ b/wadsrc/static/shaders/glsl/shadowmap.fp @@ -129,8 +129,8 @@ void main() { case 0: pixelpos = vec2((gl_FragCoord.x - 128.0) / 128.0, 1.0); break; case 1: pixelpos = vec2(1.0, (gl_FragCoord.x - 384.0) / 128.0); break; - case 2: pixelpos = vec2((gl_FragCoord.x - 640.0) / 128.0, -1.0); break; - case 3: pixelpos = vec2(-1.0, (gl_FragCoord.x - 896.0) / 128.0); break; + case 2: pixelpos = vec2(-(gl_FragCoord.x - 640.0) / 128.0, -1.0); break; + case 3: pixelpos = vec2(-1.0, -(gl_FragCoord.x - 896.0) / 128.0); break; } pixelpos = lightpos + pixelpos * radius; From 6df3b3fbca60808301649b835f0703aae0480f36 Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Tue, 7 Mar 2017 15:58:22 +0100 Subject: [PATCH 09/18] Changed the light collision structure uploaded to the GPU to be a binary tree using AABBs for the nodes instead of a BSP plane --- src/gl/dynlights/gl_lightbsp.cpp | 229 +++++++++++++++++------- src/gl/dynlights/gl_lightbsp.h | 36 +++- src/gl/dynlights/gl_shadowmap.cpp | 2 +- src/gl/renderer/gl_renderbuffers.cpp | 5 + wadsrc/static/shaders/glsl/main.fp | 2 +- wadsrc/static/shaders/glsl/shadowmap.fp | 175 +++++++++--------- 6 files changed, 293 insertions(+), 156 deletions(-) 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. -// -//=========================================================================== +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); -vec2 rayTest(vec2 from, vec2 to) + 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); From 818b72fbf55a18732e2ce05fbbdaf697f726cafc Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Wed, 8 Mar 2017 00:33:42 +0100 Subject: [PATCH 10/18] Reduce margin a little --- wadsrc/static/shaders/glsl/main.fp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wadsrc/static/shaders/glsl/main.fp b/wadsrc/static/shaders/glsl/main.fp index 07688d9df..1c146510b 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) * 5.0; // margin, to remove wall acne + dir -= sign(dir) * 2.0; // margin, to remove wall acne float dist2 = dot(dir, dir); return texture(ShadowMap, vec2(u, v)).x > dist2 ? 1.0 : 0.0; } From 850e61d1c9c7fda81da7a299cbbe087e42b8fb22 Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Wed, 8 Mar 2017 00:34:08 +0100 Subject: [PATCH 11/18] Add shadow test to sprites --- src/gl/dynlights/gl_lightbsp.cpp | 143 +++++++++++++++++++++++++----- src/gl/dynlights/gl_lightbsp.h | 11 ++- src/gl/dynlights/gl_shadowmap.cpp | 7 +- src/gl/dynlights/gl_shadowmap.h | 2 + src/gl/scene/gl_spritelight.cpp | 2 +- 5 files changed, 139 insertions(+), 26 deletions(-) diff --git a/src/gl/dynlights/gl_lightbsp.cpp b/src/gl/dynlights/gl_lightbsp.cpp index c7ae65fa1..5df10f183 100644 --- a/src/gl/dynlights/gl_lightbsp.cpp +++ b/src/gl/dynlights/gl_lightbsp.cpp @@ -80,24 +80,11 @@ void FLightBSP::UploadNodes() void FLightBSP::UploadSegs() { - TArray gpulines; - gpulines.Resize(level.lines.Size()); - for (unsigned int i = 0; i < level.lines.Size(); i++) - { - const auto &line = level.lines[i]; - auto &gpuseg = gpulines[i]; - - 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 (gpulines.Size() > 0) + if (Shape->lines.Size() > 0) { FILE *file = fopen("lines.txt", "wb"); - fwrite(&gpulines[0], sizeof(GPULine) * gpulines.Size(), 1, file); + fwrite(&Shape->lines[0], sizeof(GPULine) * Shape->lines.Size(), 1, file); fclose(file); } #endif @@ -107,7 +94,7 @@ void FLightBSP::UploadSegs() glGenBuffers(1, (GLuint*)&LinesBuffer); glBindBuffer(GL_SHADER_STORAGE_BUFFER, LinesBuffer); - glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(GPULine) * gpulines.Size(), &gpulines[0], GL_STATIC_DRAW); + glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(GPULine) * Shape->lines.Size(), &Shape->lines[0], GL_STATIC_DRAW); glBindBuffer(GL_SHADER_STORAGE_BUFFER, oldBinding); NumSegs = numsegs; @@ -128,11 +115,16 @@ void FLightBSP::Clear() Shape.reset(); } +bool FLightBSP::ShadowTest(const DVector3 &light, const DVector3 &pos) +{ + return Shape->RayTest(light, pos) >= 1.0f; +} + ///////////////////////////////////////////////////////////////////////////// Level2DShape::Level2DShape() { - TArray lines; + TArray line_elements; TArray centroids; for (unsigned int i = 0; i < level.lines.Size(); i++) { @@ -142,7 +134,7 @@ Level2DShape::Level2DShape() continue; } - lines.Push(i); + line_elements.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() }; @@ -150,12 +142,117 @@ Level2DShape::Level2DShape() } TArray work_buffer; - work_buffer.Resize(lines.Size() * 2); + work_buffer.Resize(line_elements.Size() * 2); + root = Subdivide(&line_elements[0], (int)line_elements.Size(), ¢roids[0], &work_buffer[0]); - root = subdivide(&lines[0], (int)lines.Size(), ¢roids[0], &work_buffer[0]); + lines.Resize(level.lines.Size()); + for (unsigned int i = 0; i < level.lines.Size(); i++) + { + const auto &line = level.lines[i]; + auto &gpuseg = lines[i]; + + 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; + } } -int Level2DShape::subdivide(int *lines, int num_lines, const FVector2 *centroids, int *work_buffer) +double Level2DShape::RayTest(const DVector3 &ray_start, const DVector3 &ray_end) +{ + DVector2 raydelta = ray_end - ray_start; + double raydist2 = raydelta | raydelta; + DVector2 raynormal = DVector2(raydelta.Y, -raydelta.X); + double rayd = raynormal | ray_start; + if (raydist2 < 1.0) + return 1.0f; + + double t = 1.0; + + int stack[16]; + int stack_pos = 1; + stack[0] = nodes.Size() - 1; + while (stack_pos > 0) + { + int node_index = stack[stack_pos - 1]; + + if (!OverlapRayAABB(ray_start, ray_end, nodes[node_index])) + { + stack_pos--; + } + else if (nodes[node_index].line_index != -1) // 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; +} + +bool Level2DShape::OverlapRayAABB(const DVector2 &ray_start2d, const DVector2 &ray_end2d, const GPUNode &node) +{ + // To do: simplify test to use a 2D test + DVector3 ray_start = DVector3(ray_start2d, 0.0); + DVector3 ray_end = DVector3(ray_end2d, 0.0); + DVector3 aabb_min = DVector3(node.aabb_left, node.aabb_top, -1.0); + DVector3 aabb_max = DVector3(node.aabb_right, node.aabb_bottom, 1.0); + + DVector3 c = (ray_start + ray_end) * 0.5f; + DVector3 w = ray_end - c; + DVector3 h = (aabb_max - aabb_min) * 0.5f; // aabb.extents(); + + c -= (aabb_max + aabb_min) * 0.5f; // aabb.center(); + + DVector3 v = DVector3(abs(w.X), abs(w.Y), abs(w.Z)); + + 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; +} + +double Level2DShape::IntersectRayLine(const DVector2 &ray_start, const DVector2 &ray_end, int line_index, const DVector2 &raydelta, double rayd, double raydist2) +{ + const double epsilon = 0.0000001; + const GPULine &line = lines[line_index]; + + DVector2 raynormal = DVector2(raydelta.Y, -raydelta.X); + + DVector2 line_pos(line.x, line.y); + DVector2 line_delta(line.dx, line.dy); + + double den = raynormal | line_delta; + if (abs(den) > epsilon) + { + double t_line = (rayd - (raynormal | line_pos)) / den; + if (t_line >= 0.0 && t_line <= 1.0) + { + DVector2 linehitdelta = line_pos + line_delta * t_line - ray_start; + double t = (raydelta | linehitdelta) / raydist2; + return t > 0.0 ? t : 1.0; + } + } + + return 1.0; +} + +int Level2DShape::Subdivide(int *lines, int num_lines, const FVector2 *centroids, int *work_buffer) { if (num_lines == 0) return -1; @@ -255,9 +352,9 @@ int Level2DShape::subdivide(int *lines, int num_lines, const FVector2 *centroids int left_index = -1; int right_index = -1; if (left_count > 0) - left_index = subdivide(lines, left_count, centroids, work_buffer); + left_index = Subdivide(lines, left_count, centroids, work_buffer); if (right_count > 0) - right_index = subdivide(lines + left_count, right_count, centroids, work_buffer); + 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 c6f2b826d..137813992 100644 --- a/src/gl/dynlights/gl_lightbsp.h +++ b/src/gl/dynlights/gl_lightbsp.h @@ -1,6 +1,7 @@ #pragma once +#include "vectors.h" #include struct GPUNode @@ -28,10 +29,16 @@ public: Level2DShape(); TArray nodes; + TArray lines; int root; + double RayTest(const DVector3 &ray_start, const DVector3 &ray_end); + private: - int subdivide(int *lines, int num_lines, const FVector2 *centroids, int *work_buffer); + bool OverlapRayAABB(const DVector2 &ray_start2d, const DVector2 &ray_end2d, const GPUNode &node); + double IntersectRayLine(const DVector2 &ray_start, const DVector2 &ray_end, int line_index, const DVector2 &raydelta, double rayd, double raydist2); + + int Subdivide(int *lines, int num_lines, const FVector2 *centroids, int *work_buffer); }; class FLightBSP @@ -44,6 +51,8 @@ public: int GetLinesBuffer(); void Clear(); + bool ShadowTest(const DVector3 &light, const DVector3 &pos); + private: void UpdateBuffers(); void GenerateBuffers(); diff --git a/src/gl/dynlights/gl_shadowmap.cpp b/src/gl/dynlights/gl_shadowmap.cpp index 384b616b2..d7215e9b4 100644 --- a/src/gl/dynlights/gl_shadowmap.cpp +++ b/src/gl/dynlights/gl_shadowmap.cpp @@ -105,4 +105,9 @@ void FShadowMap::UploadLights() glBindBuffer(GL_SHADER_STORAGE_BUFFER, mLightList); glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(float) * mLights.Size(), &mLights[0], GL_STATIC_DRAW); glBindBuffer(GL_SHADER_STORAGE_BUFFER, oldBinding); -} \ No newline at end of file +} + +bool FShadowMap::ShadowTest(ADynamicLight *light, const DVector3 &pos) +{ + return mLightBSP.ShadowTest(light->Pos(), pos); +} diff --git a/src/gl/dynlights/gl_shadowmap.h b/src/gl/dynlights/gl_shadowmap.h index cc32ec777..a8cc70129 100644 --- a/src/gl/dynlights/gl_shadowmap.h +++ b/src/gl/dynlights/gl_shadowmap.h @@ -17,6 +17,8 @@ public: int ShadowMapIndex(ADynamicLight *light) { return mLightToShadowmap[light]; } + bool ShadowTest(ADynamicLight *light, const DVector3 &pos); + private: void UploadLights(); diff --git a/src/gl/scene/gl_spritelight.cpp b/src/gl/scene/gl_spritelight.cpp index 6efa078ca..3283f0086 100644 --- a/src/gl/scene/gl_spritelight.cpp +++ b/src/gl/scene/gl_spritelight.cpp @@ -91,7 +91,7 @@ void gl_SetDynSpriteLight(AActor *self, float x, float y, float z, subsector_t * frac = 1.0f - (dist / radius); - if (frac > 0) + if (frac > 0 && GLRenderer->mShadowMap.ShadowTest(light, { x, y, z })) { lr = light->GetRed() / 255.0f; lg = light->GetGreen() / 255.0f; From cb40c369cdaf759891c5e58cf08d8ddfbce6adf3 Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Wed, 8 Mar 2017 01:35:07 +0100 Subject: [PATCH 12/18] Added PCF shadows --- wadsrc/static/shaders/glsl/main.fp | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/wadsrc/static/shaders/glsl/main.fp b/wadsrc/static/shaders/glsl/main.fp index 1c146510b..c3ef1f5d6 100644 --- a/wadsrc/static/shaders/glsl/main.fp +++ b/wadsrc/static/shaders/glsl/main.fp @@ -139,11 +139,10 @@ float R_DoomLightingEquation(float light) // //=========================================================================== -float shadowmapAttenuation(vec4 lightpos, float shadowIndex) +float sampleShadowmap(vec2 lightpos, vec2 testpos, float v) { float u; - float v = (abs(shadowIndex) + 0.5) / 1024.0; - vec2 dir = (pixelpos.xz - lightpos.xz); + vec2 dir = (testpos - lightpos); if (abs(dir.x) > abs(dir.y)) { if (dir.x >= 0.0) @@ -163,6 +162,28 @@ float shadowmapAttenuation(vec4 lightpos, float shadowIndex) return texture(ShadowMap, vec2(u, v)).x > dist2 ? 1.0 : 0.0; } +//=========================================================================== +// +// Check if light is in shadow using Percentage Closer Filtering (PCF) +// +//=========================================================================== + +#define PCF_FILTER_STEP_COUNT 3 +#define PCF_COUNT (PCF_FILTER_STEP_COUNT * 2 + 1) + +float shadowmapAttenuation(vec4 lightpos, float shadowIndex) +{ + float v = (abs(shadowIndex) + 0.5) / 1024.0; + vec2 dir = (pixelpos.xz - lightpos.xz); + vec2 normal = normalize(vec2(-dir.y, dir.x)); + float sum = 0.0; + for (float x = -PCF_FILTER_STEP_COUNT; x <= PCF_FILTER_STEP_COUNT; x++) + { + sum += sampleShadowmap(lightpos.xz, pixelpos.xz + normal * x, v); + } + return sum / PCF_COUNT; +} + //=========================================================================== // // Standard lambertian diffuse light calculation From 8687a9868af18788a2491cc78c62dae14fa5823e Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Wed, 8 Mar 2017 12:40:45 +0100 Subject: [PATCH 13/18] Rename Level2DShape to LevelAABBTree and add a bit of documentation to it --- src/gl/dynlights/gl_lightbsp.cpp | 106 +++++++++++++++++++------------ src/gl/dynlights/gl_lightbsp.h | 46 ++++++++++---- 2 files changed, 96 insertions(+), 56 deletions(-) diff --git a/src/gl/dynlights/gl_lightbsp.cpp b/src/gl/dynlights/gl_lightbsp.cpp index 5df10f183..0b954ffca 100644 --- a/src/gl/dynlights/gl_lightbsp.cpp +++ b/src/gl/dynlights/gl_lightbsp.cpp @@ -51,7 +51,7 @@ void FLightBSP::UpdateBuffers() void FLightBSP::GenerateBuffers() { if (!Shape) - Shape.reset(new Level2DShape()); + Shape.reset(new LevelAABBTree()); UploadNodes(); UploadSegs(); } @@ -62,7 +62,7 @@ void FLightBSP::UploadNodes() if (Shape->nodes.Size() > 0) { FILE *file = fopen("nodes.txt", "wb"); - fwrite(&Shape->nodes[0], sizeof(GPUNode) * Shape->nodes.Size(), 1, file); + fwrite(&Shape->nodes[0], sizeof(AABBTreeNode) * Shape->nodes.Size(), 1, file); fclose(file); } #endif @@ -72,7 +72,7 @@ void FLightBSP::UploadNodes() glGenBuffers(1, (GLuint*)&NodesBuffer); glBindBuffer(GL_SHADER_STORAGE_BUFFER, NodesBuffer); - glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(GPUNode) * Shape->nodes.Size(), &Shape->nodes[0], GL_STATIC_DRAW); + glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(AABBTreeNode) * Shape->nodes.Size(), &Shape->nodes[0], GL_STATIC_DRAW); glBindBuffer(GL_SHADER_STORAGE_BUFFER, oldBinding); NumNodes = numnodes; @@ -84,7 +84,7 @@ void FLightBSP::UploadSegs() if (Shape->lines.Size() > 0) { FILE *file = fopen("lines.txt", "wb"); - fwrite(&Shape->lines[0], sizeof(GPULine) * Shape->lines.Size(), 1, file); + fwrite(&Shape->lines[0], sizeof(AABBTreeLine) * Shape->lines.Size(), 1, file); fclose(file); } #endif @@ -94,7 +94,7 @@ void FLightBSP::UploadSegs() glGenBuffers(1, (GLuint*)&LinesBuffer); glBindBuffer(GL_SHADER_STORAGE_BUFFER, LinesBuffer); - glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(GPULine) * Shape->lines.Size(), &Shape->lines[0], GL_STATIC_DRAW); + glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(AABBTreeLine) * Shape->lines.Size(), &Shape->lines[0], GL_STATIC_DRAW); glBindBuffer(GL_SHADER_STORAGE_BUFFER, oldBinding); NumSegs = numsegs; @@ -122,44 +122,51 @@ bool FLightBSP::ShadowTest(const DVector3 &light, const DVector3 &pos) ///////////////////////////////////////////////////////////////////////////// -Level2DShape::Level2DShape() +LevelAABBTree::LevelAABBTree() { - TArray line_elements; + // Calculate the center of all 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; - } - - line_elements.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); } + // Create a list of level lines we want to add: + TArray line_elements; + for (unsigned int i = 0; i < level.lines.Size(); i++) + { + if (!level.lines[i].backsector) + { + line_elements.Push(i); + } + } + + // GenerateTreeNode needs a buffer where it can store line indices temporarily when sorting lines into the left and right child AABB buckets TArray work_buffer; work_buffer.Resize(line_elements.Size() * 2); - root = Subdivide(&line_elements[0], (int)line_elements.Size(), ¢roids[0], &work_buffer[0]); + // Generate the AABB tree + GenerateTreeNode(&line_elements[0], (int)line_elements.Size(), ¢roids[0], &work_buffer[0]); + + // Add the lines referenced by the leaf nodes lines.Resize(level.lines.Size()); for (unsigned int i = 0; i < level.lines.Size(); i++) { const auto &line = level.lines[i]; - auto &gpuseg = lines[i]; + auto &treeline = lines[i]; - 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; + treeline.x = (float)line.v1->fX(); + treeline.y = (float)line.v1->fY(); + treeline.dx = (float)line.v2->fX() - treeline.x; + treeline.dy = (float)line.v2->fY() - treeline.y; } } -double Level2DShape::RayTest(const DVector3 &ray_start, const DVector3 &ray_end) +double LevelAABBTree::RayTest(const DVector3 &ray_start, const DVector3 &ray_end) { + // Precalculate some of the variables used by the ray/line intersection test DVector2 raydelta = ray_end - ray_start; double raydist2 = raydelta | raydelta; DVector2 raynormal = DVector2(raydelta.Y, -raydelta.X); @@ -167,40 +174,44 @@ double Level2DShape::RayTest(const DVector3 &ray_start, const DVector3 &ray_end) if (raydist2 < 1.0) return 1.0f; - double t = 1.0; + double hit_fraction = 1.0; + // Walk the tree nodes int stack[16]; int stack_pos = 1; - stack[0] = nodes.Size() - 1; + stack[0] = nodes.Size() - 1; // root node is the last node in the list while (stack_pos > 0) { int node_index = stack[stack_pos - 1]; if (!OverlapRayAABB(ray_start, ray_end, nodes[node_index])) { + // If the ray doesn't overlap this node's AABB we're done for this subtree stack_pos--; } else if (nodes[node_index].line_index != -1) // isLeaf(node_index) { - t = MIN(IntersectRayLine(ray_start, ray_end, nodes[node_index].line_index, raydelta, rayd, raydist2), t); + // We reached a leaf node. Do a ray/line intersection test to see if we hit the line. + hit_fraction = MIN(IntersectRayLine(ray_start, ray_end, nodes[node_index].line_index, raydelta, rayd, raydist2), hit_fraction); stack_pos--; } else if (stack_pos == 16) { - stack_pos--; // stack overflow + stack_pos--; // stack overflow - tree is too deep! } else { - stack[stack_pos - 1] = nodes[node_index].left; - stack[stack_pos] = nodes[node_index].right; + // The ray overlaps the node's AABB. Examine its child nodes. + stack[stack_pos - 1] = nodes[node_index].left_node; + stack[stack_pos] = nodes[node_index].right_node; stack_pos++; } } - return t; + return hit_fraction; } -bool Level2DShape::OverlapRayAABB(const DVector2 &ray_start2d, const DVector2 &ray_end2d, const GPUNode &node) +bool LevelAABBTree::OverlapRayAABB(const DVector2 &ray_start2d, const DVector2 &ray_end2d, const AABBTreeNode &node) { // To do: simplify test to use a 2D test DVector3 ray_start = DVector3(ray_start2d, 0.0); @@ -208,6 +219,10 @@ bool Level2DShape::OverlapRayAABB(const DVector2 &ray_start2d, const DVector2 &r DVector3 aabb_min = DVector3(node.aabb_left, node.aabb_top, -1.0); DVector3 aabb_max = DVector3(node.aabb_right, node.aabb_bottom, 1.0); + // Standard 3D ray/AABB overlapping test. + // The details for the math here can be found in Real-Time Rendering, 3rd Edition. + // We could use a 2D test here instead, which would probably simplify the math. + DVector3 c = (ray_start + ray_end) * 0.5f; DVector3 w = ray_end - c; DVector3 h = (aabb_max - aabb_min) * 0.5f; // aabb.extents(); @@ -227,10 +242,16 @@ bool Level2DShape::OverlapRayAABB(const DVector2 &ray_start2d, const DVector2 &r return true; // overlap; } -double Level2DShape::IntersectRayLine(const DVector2 &ray_start, const DVector2 &ray_end, int line_index, const DVector2 &raydelta, double rayd, double raydist2) +double LevelAABBTree::IntersectRayLine(const DVector2 &ray_start, const DVector2 &ray_end, int line_index, const DVector2 &raydelta, double rayd, double raydist2) { + // Check if two line segments intersects (the ray and the line). + // The math below does this by first finding the fractional hit for an infinitely long ray line. + // If that hit is within the line segment (0 to 1 range) then it calculates the fractional hit for where the ray would hit. + // + // This algorithm is homemade - I would not be surprised if there's a much faster method out there. + const double epsilon = 0.0000001; - const GPULine &line = lines[line_index]; + const AABBTreeLine &line = lines[line_index]; DVector2 raynormal = DVector2(raydelta.Y, -raydelta.X); @@ -252,7 +273,7 @@ double Level2DShape::IntersectRayLine(const DVector2 &ray_start, const DVector2 return 1.0; } -int Level2DShape::Subdivide(int *lines, int num_lines, const FVector2 *centroids, int *work_buffer) +int LevelAABBTree::GenerateTreeNode(int *lines, int num_lines, const FVector2 *centroids, int *work_buffer) { if (num_lines == 0) return -1; @@ -285,7 +306,7 @@ int Level2DShape::Subdivide(int *lines, int num_lines, const FVector2 *centroids if (num_lines == 1) // Leaf node { - nodes.Push(GPUNode(aabb_min, aabb_max, lines[0])); + nodes.Push(AABBTreeNode(aabb_min, aabb_max, lines[0])); return (int)nodes.Size() - 1; } @@ -295,21 +316,21 @@ int Level2DShape::Subdivide(int *lines, int num_lines, const FVector2 *centroids 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 + // Try sort at longest axis, then if that fails then the other one. + // We place the sorted lines into work_buffer and then move the result back to the lines list when done. int left_count, right_count; FVector2 axis; for (int attempt = 0; attempt < 2; attempt++) { - // Find the split plane for axis + // Find the sort plane for axis FVector2 axis = axis_plane[axis_order[attempt]]; FVector3 plane(axis, -(median | axis)); - // Split lines into two + // Sort lines into two based ib whether the line center is on the front or back side of a plane left_count = 0; right_count = 0; for (int i = 0; i < num_lines; i++) @@ -333,7 +354,7 @@ int Level2DShape::Subdivide(int *lines, int num_lines, const FVector2 *centroids break; } - // Check if something went wrong when splitting and do a random split instead + // Check if something went wrong when sorting and do a random sort instead if (left_count == 0 || right_count == 0) { left_count = num_lines / 2; @@ -352,10 +373,11 @@ int Level2DShape::Subdivide(int *lines, int num_lines, const FVector2 *centroids int left_index = -1; int right_index = -1; if (left_count > 0) - left_index = Subdivide(lines, left_count, centroids, work_buffer); + left_index = GenerateTreeNode(lines, left_count, centroids, work_buffer); if (right_count > 0) - right_index = Subdivide(lines + left_count, right_count, centroids, work_buffer); + right_index = GenerateTreeNode(lines + left_count, right_count, centroids, work_buffer); - nodes.Push(GPUNode(aabb_min, aabb_max, left_index, right_index)); + // Store resulting node and return its index + nodes.Push(AABBTreeNode(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 137813992..14728905d 100644 --- a/src/gl/dynlights/gl_lightbsp.h +++ b/src/gl/dynlights/gl_lightbsp.h @@ -4,41 +4,59 @@ #include "vectors.h" #include -struct GPUNode +// Node in a binary AABB tree +struct AABBTreeNode { - 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) { } + AABBTreeNode(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_node(-1), right_node(-1), line_index(line_index) { } + AABBTreeNode(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_node(left), right_node(right), line_index(-1) { } + // Axis aligned bounding box for the node float aabb_left, aabb_top; float aabb_right, aabb_bottom; - int left; - int right; + + // Children node indices + int left_node; + int right_node; + + // AABBTreeLine index if it is a leaf node. Index is -1 if it is not. int line_index; + + // Padding to keep 16-byte length (this structure is uploaded to the GPU) int padding; }; -struct GPULine +// Line segment for leaf nodes in an AABB tree +struct AABBTreeLine { float x, y; float dx, dy; }; -class Level2DShape +// Axis aligned bounding box tree used for ray testing lines. +class LevelAABBTree { public: - Level2DShape(); + // Constructs a tree for the current level + LevelAABBTree(); - TArray nodes; - TArray lines; - int root; + // Nodes in the AABB tree. Last node is the root node. + TArray nodes; + // Line segments for the leaf nodes in the tree. + TArray lines; + + // Shoot a ray from ray_start to ray_end and return the first hit as a fractional value between 0 and 1. Returns 1 if no line was hit. double RayTest(const DVector3 &ray_start, const DVector3 &ray_end); private: - bool OverlapRayAABB(const DVector2 &ray_start2d, const DVector2 &ray_end2d, const GPUNode &node); + // Test if a ray overlaps an AABB node or not + bool OverlapRayAABB(const DVector2 &ray_start2d, const DVector2 &ray_end2d, const AABBTreeNode &node); + + // Intersection test between a ray and a line segment double IntersectRayLine(const DVector2 &ray_start, const DVector2 &ray_end, int line_index, const DVector2 &raydelta, double rayd, double raydist2); - int Subdivide(int *lines, int num_lines, const FVector2 *centroids, int *work_buffer); + // Generate a tree node and its children recursively + int GenerateTreeNode(int *lines, int num_lines, const FVector2 *centroids, int *work_buffer); }; class FLightBSP @@ -67,5 +85,5 @@ private: int NumNodes = 0; int NumSegs = 0; - std::unique_ptr Shape; + std::unique_ptr Shape; }; From d09c3ad305fe01d4af08b1593a9b3145d8b4af9b Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Wed, 8 Mar 2017 13:31:19 +0100 Subject: [PATCH 14/18] Move all GPU handling to gl_shadowmap and rename gl_lightbsp to gl_aabbtree --- src/CMakeLists.txt | 2 +- .../{gl_lightbsp.cpp => gl_aabbtree.cpp} | 99 +------------------ .../{gl_lightbsp.h => gl_aabbtree.h} | 34 +------ src/gl/dynlights/gl_shadowmap.cpp | 73 +++++++++++--- src/gl/dynlights/gl_shadowmap.h | 13 ++- 5 files changed, 74 insertions(+), 147 deletions(-) rename src/gl/dynlights/{gl_lightbsp.cpp => gl_aabbtree.cpp} (80%) rename src/gl/dynlights/{gl_lightbsp.h => gl_aabbtree.h} (73%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2cd811b67..31ff9f470 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -939,7 +939,7 @@ set( FASTMATH_SOURCES gl/dynlights/gl_glow.cpp gl/dynlights/gl_dynlight1.cpp gl/dynlights/gl_lightbuffer.cpp - gl/dynlights/gl_lightbsp.cpp + gl/dynlights/gl_aabbtree.cpp gl/dynlights/gl_shadowmap.cpp gl/shaders/gl_shader.cpp gl/shaders/gl_texshader.cpp diff --git a/src/gl/dynlights/gl_lightbsp.cpp b/src/gl/dynlights/gl_aabbtree.cpp similarity index 80% rename from src/gl/dynlights/gl_lightbsp.cpp rename to src/gl/dynlights/gl_aabbtree.cpp index 0b954ffca..7108a7278 100644 --- a/src/gl/dynlights/gl_lightbsp.cpp +++ b/src/gl/dynlights/gl_aabbtree.cpp @@ -1,6 +1,6 @@ // //--------------------------------------------------------------------------- -// 2D collision tree for 1D shadowmap lights +// AABB-tree used for ray testing // Copyright(C) 2017 Magnus Norddahl // All rights reserved. // @@ -22,106 +22,11 @@ #include "gl/system/gl_system.h" #include "gl/shaders/gl_shader.h" -#include "gl/dynlights/gl_lightbsp.h" +#include "gl/dynlights/gl_aabbtree.h" #include "gl/system/gl_interface.h" #include "r_state.h" #include "g_levellocals.h" -int FLightBSP::GetNodesBuffer() -{ - UpdateBuffers(); - return NodesBuffer; -} - -int FLightBSP::GetLinesBuffer() -{ - UpdateBuffers(); - return LinesBuffer; -} - -void FLightBSP::UpdateBuffers() -{ - if (numnodes != NumNodes || numsegs != NumSegs) // To do: there is probably a better way to detect a map change than this.. - Clear(); - - if (NodesBuffer == 0) - GenerateBuffers(); -} - -void FLightBSP::GenerateBuffers() -{ - if (!Shape) - Shape.reset(new LevelAABBTree()); - UploadNodes(); - UploadSegs(); -} - -void FLightBSP::UploadNodes() -{ -#if 0 - if (Shape->nodes.Size() > 0) - { - FILE *file = fopen("nodes.txt", "wb"); - fwrite(&Shape->nodes[0], sizeof(AABBTreeNode) * Shape->nodes.Size(), 1, file); - fclose(file); - } -#endif - - int oldBinding = 0; - glGetIntegerv(GL_SHADER_STORAGE_BUFFER_BINDING, &oldBinding); - - glGenBuffers(1, (GLuint*)&NodesBuffer); - glBindBuffer(GL_SHADER_STORAGE_BUFFER, NodesBuffer); - glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(AABBTreeNode) * Shape->nodes.Size(), &Shape->nodes[0], GL_STATIC_DRAW); - glBindBuffer(GL_SHADER_STORAGE_BUFFER, oldBinding); - - NumNodes = numnodes; -} - -void FLightBSP::UploadSegs() -{ -#if 0 - if (Shape->lines.Size() > 0) - { - FILE *file = fopen("lines.txt", "wb"); - fwrite(&Shape->lines[0], sizeof(AABBTreeLine) * Shape->lines.Size(), 1, file); - fclose(file); - } -#endif - - int oldBinding = 0; - glGetIntegerv(GL_SHADER_STORAGE_BUFFER_BINDING, &oldBinding); - - glGenBuffers(1, (GLuint*)&LinesBuffer); - glBindBuffer(GL_SHADER_STORAGE_BUFFER, LinesBuffer); - glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(AABBTreeLine) * Shape->lines.Size(), &Shape->lines[0], GL_STATIC_DRAW); - glBindBuffer(GL_SHADER_STORAGE_BUFFER, oldBinding); - - NumSegs = numsegs; -} - -void FLightBSP::Clear() -{ - if (NodesBuffer != 0) - { - glDeleteBuffers(1, (GLuint*)&NodesBuffer); - NodesBuffer = 0; - } - if (LinesBuffer != 0) - { - glDeleteBuffers(1, (GLuint*)&LinesBuffer); - LinesBuffer = 0; - } - Shape.reset(); -} - -bool FLightBSP::ShadowTest(const DVector3 &light, const DVector3 &pos) -{ - return Shape->RayTest(light, pos) >= 1.0f; -} - -///////////////////////////////////////////////////////////////////////////// - LevelAABBTree::LevelAABBTree() { // Calculate the center of all lines diff --git a/src/gl/dynlights/gl_lightbsp.h b/src/gl/dynlights/gl_aabbtree.h similarity index 73% rename from src/gl/dynlights/gl_lightbsp.h rename to src/gl/dynlights/gl_aabbtree.h index 14728905d..0de075be5 100644 --- a/src/gl/dynlights/gl_lightbsp.h +++ b/src/gl/dynlights/gl_aabbtree.h @@ -2,7 +2,6 @@ #pragma once #include "vectors.h" -#include // Node in a binary AABB tree struct AABBTreeNode @@ -14,7 +13,7 @@ struct AABBTreeNode float aabb_left, aabb_top; float aabb_right, aabb_bottom; - // Children node indices + // Child node indices int left_node; int right_node; @@ -45,7 +44,7 @@ public: // Line segments for the leaf nodes in the tree. TArray lines; - // Shoot a ray from ray_start to ray_end and return the first hit as a fractional value between 0 and 1. Returns 1 if no line was hit. + // Shoot a ray from ray_start to ray_end and return the closest hit as a fractional value between 0 and 1. Returns 1 if no line was hit. double RayTest(const DVector3 &ray_start, const DVector3 &ray_end); private: @@ -58,32 +57,3 @@ private: // Generate a tree node and its children recursively int GenerateTreeNode(int *lines, int num_lines, const FVector2 *centroids, int *work_buffer); }; - -class FLightBSP -{ -public: - FLightBSP() { } - ~FLightBSP() { Clear(); } - - int GetNodesBuffer(); - int GetLinesBuffer(); - void Clear(); - - bool ShadowTest(const DVector3 &light, const DVector3 &pos); - -private: - void UpdateBuffers(); - void GenerateBuffers(); - void UploadNodes(); - void UploadSegs(); - - FLightBSP(const FLightBSP &) = delete; - FLightBSP &operator=(FLightBSP &) = delete; - - int NodesBuffer = 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 d7215e9b4..8f8b74f44 100644 --- a/src/gl/dynlights/gl_shadowmap.cpp +++ b/src/gl/dynlights/gl_shadowmap.cpp @@ -32,19 +32,9 @@ #include "gl/shaders/gl_shadowmapshader.h" #include "r_state.h" -void FShadowMap::Clear() -{ - if (mLightList != 0) - { - glDeleteBuffers(1, (GLuint*)&mLightList); - mLightList = 0; - } - - mLightBSP.Clear(); -} - void FShadowMap::Update() { + UploadAABBTree(); UploadLights(); FGLDebug::PushGroup("ShadowMap"); @@ -54,8 +44,8 @@ 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.GetLinesBuffer()); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, mNodesBuffer); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, mLinesBuffer); glViewport(0, 0, 1024, 1024); GLRenderer->RenderScreenQuad(); @@ -72,6 +62,14 @@ void FShadowMap::Update() FGLDebug::PopGroup(); } +bool FShadowMap::ShadowTest(ADynamicLight *light, const DVector3 &pos) +{ + if (mAABBTree) + return mAABBTree->RayTest(light->Pos(), pos) >= 1.0f; + else + return true; +} + void FShadowMap::UploadLights() { mLights.Clear(); @@ -107,7 +105,52 @@ void FShadowMap::UploadLights() glBindBuffer(GL_SHADER_STORAGE_BUFFER, oldBinding); } -bool FShadowMap::ShadowTest(ADynamicLight *light, const DVector3 &pos) +void FShadowMap::UploadAABBTree() { - return mLightBSP.ShadowTest(light->Pos(), pos); + if (numnodes != mLastNumNodes || numsegs != mLastNumSegs) // To do: there is probably a better way to detect a map change than this.. + Clear(); + + if (mAABBTree) + return; + + mAABBTree.reset(new LevelAABBTree()); + + int oldBinding = 0; + glGetIntegerv(GL_SHADER_STORAGE_BUFFER_BINDING, &oldBinding); + + glGenBuffers(1, (GLuint*)&mNodesBuffer); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, mNodesBuffer); + glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(AABBTreeNode) * mAABBTree->nodes.Size(), &mAABBTree->nodes[0], GL_STATIC_DRAW); + + glGenBuffers(1, (GLuint*)&mLinesBuffer); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, mLinesBuffer); + glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(AABBTreeLine) * mAABBTree->lines.Size(), &mAABBTree->lines[0], GL_STATIC_DRAW); + + glBindBuffer(GL_SHADER_STORAGE_BUFFER, oldBinding); +} + +void FShadowMap::Clear() +{ + if (mLightList != 0) + { + glDeleteBuffers(1, (GLuint*)&mLightList); + mLightList = 0; + } + + if (mNodesBuffer != 0) + { + glDeleteBuffers(1, (GLuint*)&mNodesBuffer); + mNodesBuffer = 0; + } + + if (mLinesBuffer != 0) + { + glDeleteBuffers(1, (GLuint*)&mLinesBuffer); + mLinesBuffer = 0; + } + + mAABBTree.reset(); + + mLastNumNodes = numnodes; + mLastNumSegs = numsegs; } diff --git a/src/gl/dynlights/gl_shadowmap.h b/src/gl/dynlights/gl_shadowmap.h index a8cc70129..b999a550f 100644 --- a/src/gl/dynlights/gl_shadowmap.h +++ b/src/gl/dynlights/gl_shadowmap.h @@ -1,8 +1,9 @@ #pragma once -#include "gl/dynlights/gl_lightbsp.h" +#include "gl/dynlights/gl_aabbtree.h" #include "tarray.h" +#include class ADynamicLight; @@ -20,13 +21,21 @@ public: bool ShadowTest(ADynamicLight *light, const DVector3 &pos); private: + void UploadAABBTree(); void UploadLights(); - FLightBSP mLightBSP; int mLightList = 0; TArray mLights; TMap mLightToShadowmap; + int mNodesBuffer = 0; + int mLinesBuffer = 0; + + int mLastNumNodes = 0; + int mLastNumSegs = 0; + + std::unique_ptr mAABBTree; + FShadowMap(const FShadowMap &) = delete; FShadowMap &operator=(FShadowMap &) = delete; }; From b281a697ce3666f2c771d3e2f5d070ac573b8f6a Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Wed, 8 Mar 2017 14:12:59 +0100 Subject: [PATCH 15/18] Document the algorithm used for generating the 1D shadow maps --- src/gl/dynlights/gl_shadowmap.cpp | 33 +++++++++++++++++++++++++++++++ src/gl/dynlights/gl_shadowmap.h | 16 +++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/src/gl/dynlights/gl_shadowmap.cpp b/src/gl/dynlights/gl_shadowmap.cpp index 8f8b74f44..d43738f85 100644 --- a/src/gl/dynlights/gl_shadowmap.cpp +++ b/src/gl/dynlights/gl_shadowmap.cpp @@ -32,6 +32,39 @@ #include "gl/shaders/gl_shadowmapshader.h" #include "r_state.h" +/* + The 1D shadow maps are stored in a 1024x1024 texture as float depth values (R32F). + + Each line in the texture is assigned to a single light. For example, to grab depth values for light 20 + the fragment shader (main.fp) needs to sample from row 20. That is, the V texture coordinate needs + to be 20.5/1024. + + mLightToShadowmap is a hash map storing which line each ADynamicLight is assigned to. The public + ShadowMapIndex function allows the main rendering to find the index and upload that along with the + normal light data. From there, the main.fp shader can sample from the shadow map texture, which + is currently always bound to texture unit 16. + + The texel row for each light is split into four parts. One for each direction, like a cube texture, + but then only in 2D where this reduces itself to a square. When main.fp samples from the shadow map + it first decides in which direction the fragment is (relative to the light), like cubemap sampling does + for 3D, but once again just for the 2D case. + + Texels 0-255 is Y positive, 256-511 is X positive, 512-767 is Y negative and 768-1023 is X negative. + + Generating the shadow map itself is done by FShadowMap::Update(). The shadow map texture's FBO is + bound and then a screen quad is drawn to make a fragment shader cover all texels. For each fragment + it shoots a ray and collects the distance to what it hit. + + The shadowmap.fp shader knows which light and texel it is processing by mapping gl_FragCoord.y back + to the light index, and it knows which direction to ray trace by looking at gl_FragCoord.x. For + example, if gl_FragCoord.y is 20.5, then it knows its processing light 20, and if gl_FragCoord.x is + 127.5, then it knows we are shooting straight ahead for the Y positive direction. + + Ray testing is done by uploading two GPU storage buffers - one holding AABB tree nodes, and one with + the line segments at the leaf nodes of the tree. The fragment shader then performs a test same way + as on the CPU, except everything uses indexes as pointers are not allowed in GLSL. +*/ + void FShadowMap::Update() { UploadAABBTree(); diff --git a/src/gl/dynlights/gl_shadowmap.h b/src/gl/dynlights/gl_shadowmap.h index b999a550f..da63b37e7 100644 --- a/src/gl/dynlights/gl_shadowmap.h +++ b/src/gl/dynlights/gl_shadowmap.h @@ -13,27 +13,43 @@ public: FShadowMap() { } ~FShadowMap() { Clear(); } + // Release resources void Clear(); + + // Update shadow map texture void Update(); + // Return the assigned shadow map index for a given light int ShadowMapIndex(ADynamicLight *light) { return mLightToShadowmap[light]; } + // Test if a world position is in shadow relative to the specified light and returns false if it is bool ShadowTest(ADynamicLight *light, const DVector3 &pos); private: + // Upload the AABB-tree to the GPU void UploadAABBTree(); + + // Upload light list to the GPU void UploadLights(); + // OpenGL storage buffer with the list of lights in the shadow map texture int mLightList = 0; + + // Working buffer for creating the list of lights. Stored here to avoid allocating memory each frame TArray mLights; + + // The assigned shadow map index for each light TMap mLightToShadowmap; + // OpenGL storage buffers for the AABB tree int mNodesBuffer = 0; int mLinesBuffer = 0; + // Used to detect when a level change requires the AABB tree to be regenerated int mLastNumNodes = 0; int mLastNumSegs = 0; + // AABB-tree of the level, used for ray tests std::unique_ptr mAABBTree; FShadowMap(const FShadowMap &) = delete; From b66049305121f803dcb9241df9b7a54018f1195e Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Fri, 10 Mar 2017 19:10:40 +0100 Subject: [PATCH 16/18] Add menu option for disabling shadow maps and detecting if storage buffers are available or not --- src/gl/dynlights/gl_dynlight1.cpp | 1 + src/gl/dynlights/gl_shadowmap.cpp | 19 ++++++++++++++++++- src/gl/dynlights/gl_shadowmap.h | 5 ++++- src/gl/shaders/gl_shader.cpp | 5 +++++ src/gl/system/gl_cvars.h | 1 + wadsrc/static/language.enu | 1 + wadsrc/static/menudef.zz | 1 + wadsrc/static/shaders/glsl/main.fp | 9 +++++++++ 8 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/gl/dynlights/gl_dynlight1.cpp b/src/gl/dynlights/gl_dynlight1.cpp index 4037545aa..26f42d7a2 100644 --- a/src/gl/dynlights/gl_dynlight1.cpp +++ b/src/gl/dynlights/gl_dynlight1.cpp @@ -59,6 +59,7 @@ CVAR (Bool, gl_attachedlights, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); CVAR (Bool, gl_lights_checkside, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); CVAR (Bool, gl_light_sprites, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); CVAR (Bool, gl_light_particles, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); +CVAR (Bool, gl_light_shadowmap, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); //========================================================================== // diff --git a/src/gl/dynlights/gl_shadowmap.cpp b/src/gl/dynlights/gl_shadowmap.cpp index d43738f85..9a23e4114 100644 --- a/src/gl/dynlights/gl_shadowmap.cpp +++ b/src/gl/dynlights/gl_shadowmap.cpp @@ -26,6 +26,7 @@ #include "gl/dynlights/gl_dynlight.h" #include "gl/system/gl_interface.h" #include "gl/system/gl_debug.h" +#include "gl/system/gl_cvars.h" #include "gl/renderer/gl_renderer.h" #include "gl/renderer/gl_postprocessstate.h" #include "gl/renderer/gl_renderbuffers.h" @@ -67,6 +68,9 @@ void FShadowMap::Update() { + if (!IsEnabled()) + return; + UploadAABBTree(); UploadLights(); @@ -97,12 +101,25 @@ void FShadowMap::Update() bool FShadowMap::ShadowTest(ADynamicLight *light, const DVector3 &pos) { - if (mAABBTree) + if (IsEnabled() && mAABBTree) return mAABBTree->RayTest(light->Pos(), pos) >= 1.0f; else return true; } +bool FShadowMap::IsEnabled() const +{ + return gl_light_shadowmap && !!(gl.flags & RFL_SHADER_STORAGE_BUFFER); +} + +int FShadowMap::ShadowMapIndex(ADynamicLight *light) +{ + if (IsEnabled()) + return mLightToShadowmap[light]; + else + return 1024; +} + void FShadowMap::UploadLights() { mLights.Clear(); diff --git a/src/gl/dynlights/gl_shadowmap.h b/src/gl/dynlights/gl_shadowmap.h index da63b37e7..e2b5b371f 100644 --- a/src/gl/dynlights/gl_shadowmap.h +++ b/src/gl/dynlights/gl_shadowmap.h @@ -20,11 +20,14 @@ public: void Update(); // Return the assigned shadow map index for a given light - int ShadowMapIndex(ADynamicLight *light) { return mLightToShadowmap[light]; } + int ShadowMapIndex(ADynamicLight *light); // Test if a world position is in shadow relative to the specified light and returns false if it is bool ShadowTest(ADynamicLight *light, const DVector3 &pos); + // Returns true if gl_light_shadowmap is enabled and supported by the hardware + bool IsEnabled() const; + private: // Upload the AABB-tree to the GPU void UploadAABBTree(); diff --git a/src/gl/shaders/gl_shader.cpp b/src/gl/shaders/gl_shader.cpp index d36f4c4fa..9cb5e9aab 100644 --- a/src/gl/shaders/gl_shader.cpp +++ b/src/gl/shaders/gl_shader.cpp @@ -105,6 +105,11 @@ bool FShader::Load(const char * name, const char * vert_prog_lump, const char * vp_comb << "#define USE_QUAD_DRAWER\n"; } + if (!!(gl.flags & RFL_SHADER_STORAGE_BUFFER)) + { + vp_comb << "#define SUPPORTS_SHADOWMAPS\n"; + } + vp_comb << defines << i_data.GetString().GetChars(); FString fp_comb = vp_comb; diff --git a/src/gl/system/gl_cvars.h b/src/gl/system/gl_cvars.h index 3b425d261..b7122b01c 100644 --- a/src/gl/system/gl_cvars.h +++ b/src/gl/system/gl_cvars.h @@ -26,6 +26,7 @@ EXTERN_CVAR (Bool, gl_attachedlights); EXTERN_CVAR (Bool, gl_lights_checkside); EXTERN_CVAR (Bool, gl_light_sprites); EXTERN_CVAR (Bool, gl_light_particles); +EXTERN_CVAR (Bool, gl_light_shadowmap); EXTERN_CVAR(Int, gl_fogmode) EXTERN_CVAR(Int, gl_lightmode) diff --git a/wadsrc/static/language.enu b/wadsrc/static/language.enu index 55477ee2e..5f7b9f600 100644 --- a/wadsrc/static/language.enu +++ b/wadsrc/static/language.enu @@ -2656,6 +2656,7 @@ GLLIGHTMNU_LIGHTDEFS = "Enable light definitions"; GLLIGHTMNU_CLIPLIGHTS = "Clip lights"; GLLIGHTMNU_LIGHTSPRITES = "Lights affect sprites"; GLLIGHTMNU_LIGHTPARTICLES = "Lights affect particles"; +GLLIGHTMNU_LIGHTSHADOWMAP = "Light shadowmaps"; // OpenGL Preferences GLPREFMNU_TITLE = "OPENGL PREFERENCES"; diff --git a/wadsrc/static/menudef.zz b/wadsrc/static/menudef.zz index 84ddb9448..054c5ac67 100644 --- a/wadsrc/static/menudef.zz +++ b/wadsrc/static/menudef.zz @@ -226,6 +226,7 @@ OptionMenu "GLLightOptions" Option "$GLLIGHTMNU_CLIPLIGHTS", gl_lights_checkside, "YesNo" Option "$GLLIGHTMNU_LIGHTSPRITES", gl_light_sprites, "YesNo" Option "$GLLIGHTMNU_LIGHTPARTICLES", gl_light_particles, "YesNo" + Option "$GLLIGHTMNU_LIGHTSHADOWMAP", gl_light_shadowmap, "YesNo" } OptionMenu "GLPrefOptions" diff --git a/wadsrc/static/shaders/glsl/main.fp b/wadsrc/static/shaders/glsl/main.fp index c3ef1f5d6..b6bb0d9b0 100644 --- a/wadsrc/static/shaders/glsl/main.fp +++ b/wadsrc/static/shaders/glsl/main.fp @@ -139,6 +139,8 @@ float R_DoomLightingEquation(float light) // //=========================================================================== +#ifdef SUPPORTS_SHADOWMAPS + float sampleShadowmap(vec2 lightpos, vec2 testpos, float v) { float u; @@ -173,6 +175,9 @@ float sampleShadowmap(vec2 lightpos, vec2 testpos, float v) float shadowmapAttenuation(vec4 lightpos, float shadowIndex) { + if (shadowIndex <= -1024.0 || shadowIndex >= 1024.0) + return 1.0; // No shadowmap available for this light + float v = (abs(shadowIndex) + 0.5) / 1024.0; vec2 dir = (pixelpos.xz - lightpos.xz); vec2 normal = normalize(vec2(-dir.y, dir.x)); @@ -184,6 +189,8 @@ float shadowmapAttenuation(vec4 lightpos, float shadowIndex) return sum / PCF_COUNT; } +#endif + //=========================================================================== // // Standard lambertian diffuse light calculation @@ -206,7 +213,9 @@ float diffuseContribution(vec3 lightDirection, vec3 normal) float pointLightAttenuation(vec4 lightpos, float shadowIndex) { float attenuation = max(lightpos.w - distance(pixelpos.xyz, lightpos.xyz),0.0) / lightpos.w; +#ifdef SUPPORTS_SHADOWMAPS attenuation *= shadowmapAttenuation(lightpos, shadowIndex); +#endif if (shadowIndex >= 0.0) // Sign bit is the attenuated light flag { return attenuation; From 59ec97d2d56d7d16adc37051de3ef61ea1c6bf33 Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Fri, 10 Mar 2017 22:08:55 +0100 Subject: [PATCH 17/18] Fix shadow map acne and the attenuate flag --- src/gl/dynlights/gl_dynlight1.cpp | 5 +++-- wadsrc/static/shaders/glsl/main.fp | 31 ++++++++++++++++++++---------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/gl/dynlights/gl_dynlight1.cpp b/src/gl/dynlights/gl_dynlight1.cpp index 26f42d7a2..5e06456cb 100644 --- a/src/gl/dynlights/gl_dynlight1.cpp +++ b/src/gl/dynlights/gl_dynlight1.cpp @@ -109,8 +109,9 @@ bool gl_GetLight(int group, Plane & p, ADynamicLight * light, bool checkside, FD i = 1; } - float shadowIndex = (float)GLRenderer->mShadowMap.ShadowMapIndex(light); - if (!!(light->flags4 & MF4_ATTENUATE)) // Store attenuate flag in the sign bit of the float + // Store attenuate flag in the sign bit of the float. + float shadowIndex = GLRenderer->mShadowMap.ShadowMapIndex(light) + 1.0f; + if (!!(light->flags4 & MF4_ATTENUATE)) shadowIndex = -shadowIndex; float *data = &ldata.arrays[i][ldata.arrays[i].Reserve(8)]; diff --git a/wadsrc/static/shaders/glsl/main.fp b/wadsrc/static/shaders/glsl/main.fp index b6bb0d9b0..9a682f386 100644 --- a/wadsrc/static/shaders/glsl/main.fp +++ b/wadsrc/static/shaders/glsl/main.fp @@ -141,10 +141,9 @@ float R_DoomLightingEquation(float light) #ifdef SUPPORTS_SHADOWMAPS -float sampleShadowmap(vec2 lightpos, vec2 testpos, float v) +float sampleShadowmap(vec2 dir, float v) { float u; - vec2 dir = (testpos - lightpos); if (abs(dir.x) > abs(dir.y)) { if (dir.x >= 0.0) @@ -159,7 +158,6 @@ float sampleShadowmap(vec2 lightpos, vec2 testpos, float v) else u = dir.x / dir.y * 0.125 + (0.50 + 0.125); } - dir -= sign(dir) * 2.0; // margin, to remove wall acne float dist2 = dot(dir, dir); return texture(ShadowMap, vec2(u, v)).x > dist2 ? 1.0 : 0.0; } @@ -175,16 +173,28 @@ float sampleShadowmap(vec2 lightpos, vec2 testpos, float v) float shadowmapAttenuation(vec4 lightpos, float shadowIndex) { - if (shadowIndex <= -1024.0 || shadowIndex >= 1024.0) + if (shadowIndex >= 1024.0) return 1.0; // No shadowmap available for this light - float v = (abs(shadowIndex) + 0.5) / 1024.0; - vec2 dir = (pixelpos.xz - lightpos.xz); - vec2 normal = normalize(vec2(-dir.y, dir.x)); + float v = (shadowIndex + 0.5) / 1024.0; + + vec2 ray = pixelpos.xz - lightpos.xz; + float length = length(ray); + if (length < 3.0) + return 1.0; + + vec2 dir = ray / length; + + ray -= dir * 2.0; // margin + dir = dir * min(length / 50.0, 1.0); // avoid sampling behind light + + vec2 normal = vec2(-dir.y, dir.x); + vec2 bias = dir * 10.0; + float sum = 0.0; for (float x = -PCF_FILTER_STEP_COUNT; x <= PCF_FILTER_STEP_COUNT; x++) { - sum += sampleShadowmap(lightpos.xz, pixelpos.xz + normal * x, v); + sum += sampleShadowmap(ray + normal * x - bias * abs(x), v); } return sum / PCF_COUNT; } @@ -210,13 +220,14 @@ float diffuseContribution(vec3 lightDirection, vec3 normal) // //=========================================================================== -float pointLightAttenuation(vec4 lightpos, float shadowIndex) +float pointLightAttenuation(vec4 lightpos, float lightcolorA) { float attenuation = max(lightpos.w - distance(pixelpos.xyz, lightpos.xyz),0.0) / lightpos.w; #ifdef SUPPORTS_SHADOWMAPS + float shadowIndex = abs(lightcolorA) - 1.0; attenuation *= shadowmapAttenuation(lightpos, shadowIndex); #endif - if (shadowIndex >= 0.0) // Sign bit is the attenuated light flag + if (lightcolorA >= 0.0) // Sign bit is the attenuated light flag { return attenuation; } From b407ea2164f1a03facd855d1312a192faca4596c Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Fri, 10 Mar 2017 22:12:13 +0100 Subject: [PATCH 18/18] Change gl_light_shadowmap to default to being off --- src/gl/dynlights/gl_dynlight1.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gl/dynlights/gl_dynlight1.cpp b/src/gl/dynlights/gl_dynlight1.cpp index 5e06456cb..ea8eb328a 100644 --- a/src/gl/dynlights/gl_dynlight1.cpp +++ b/src/gl/dynlights/gl_dynlight1.cpp @@ -59,7 +59,7 @@ CVAR (Bool, gl_attachedlights, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); CVAR (Bool, gl_lights_checkside, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); CVAR (Bool, gl_light_sprites, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); CVAR (Bool, gl_light_particles, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); -CVAR (Bool, gl_light_shadowmap, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); +CVAR (Bool, gl_light_shadowmap, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); //========================================================================== //