Merge branch 'shadowmaps' of https://github.com/raa-eruanna/qzdoom into 3.0_work

# Conflicts:
#	src/CMakeLists.txt
#	wadsrc/static/language.enu
This commit is contained in:
Christoph Oelckers 2017-03-11 19:55:43 +01:00
commit 9eae422dab
20 changed files with 994 additions and 5 deletions

View file

@ -1104,6 +1104,8 @@ set (PCH_SOURCES
gl/dynlights/gl_dynlight.cpp
gl/dynlights/gl_glow.cpp
gl/dynlights/gl_lightbuffer.cpp
gl/dynlights/gl_aabbtree.cpp
gl/dynlights/gl_shadowmap.cpp
gl/models/gl_models_md3.cpp
gl/models/gl_models_md2.cpp
gl/models/gl_voxels.cpp
@ -1117,6 +1119,7 @@ set (PCH_SOURCES
gl/shaders/gl_shader.cpp
gl/shaders/gl_texshader.cpp
gl/shaders/gl_shaderprogram.cpp
gl/shaders/gl_shadowmapshader.cpp
gl/shaders/gl_presentshader.cpp
gl/shaders/gl_present3dRowshader.cpp
gl/shaders/gl_bloomshader.cpp

View file

@ -0,0 +1,288 @@
//
//---------------------------------------------------------------------------
// AABB-tree used for ray testing
// 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_aabbtree.h"
#include "gl/system/gl_interface.h"
#include "r_state.h"
#include "g_levellocals.h"
LevelAABBTree::LevelAABBTree()
{
// Calculate the center of all lines
TArray<FVector2> centroids;
for (unsigned int i = 0; i < level.lines.Size(); 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<int> 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<int> work_buffer;
work_buffer.Resize(line_elements.Size() * 2);
// Generate the AABB tree
GenerateTreeNode(&line_elements[0], (int)line_elements.Size(), &centroids[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 &treeline = lines[i];
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 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);
double rayd = raynormal | ray_start;
if (raydist2 < 1.0)
return 1.0f;
double hit_fraction = 1.0;
// Walk the tree nodes
int stack[16];
int stack_pos = 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)
{
// 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 - tree is too deep!
}
else
{
// 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 hit_fraction;
}
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);
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);
// 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();
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 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 AABBTreeLine &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 LevelAABBTree::GenerateTreeNode(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(AABBTreeNode(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 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 sort plane for axis
FVector2 axis = axis_plane[axis_order[attempt]];
FVector3 plane(axis, -(median | axis));
// 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++)
{
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 sorting and do a random sort 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 = GenerateTreeNode(lines, left_count, centroids, work_buffer);
if (right_count > 0)
right_index = GenerateTreeNode(lines + left_count, right_count, centroids, work_buffer);
// Store resulting node and return its index
nodes.Push(AABBTreeNode(aabb_min, aabb_max, left_index, right_index));
return (int)nodes.Size() - 1;
}

View file

@ -0,0 +1,59 @@
#pragma once
#include "vectors.h"
// Node in a binary AABB tree
struct AABBTreeNode
{
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;
// Child 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;
};
// Line segment for leaf nodes in an AABB tree
struct AABBTreeLine
{
float x, y;
float dx, dy;
};
// Axis aligned bounding box tree used for ray testing lines.
class LevelAABBTree
{
public:
// Constructs a tree for the current level
LevelAABBTree();
// Nodes in the AABB tree. Last node is the root node.
TArray<AABBTreeNode> nodes;
// Line segments for the leaf nodes in the tree.
TArray<AABBTreeLine> lines;
// 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:
// 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);
// Generate a tree node and its children recursively
int GenerateTreeNode(int *lines, int num_lines, const FVector2 *centroids, int *work_buffer);
};

View file

@ -60,6 +60,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, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG);
//==========================================================================
//
@ -109,6 +110,11 @@ bool gl_GetLight(int group, Plane & p, ADynamicLight * light, bool checkside, FD
i = 1;
}
// 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)];
data[0] = pos.X;
data[1] = pos.Z;
@ -117,7 +123,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;
}

View file

@ -0,0 +1,206 @@
//
//---------------------------------------------------------------------------
// 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/system/gl_cvars.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"
/*
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()
{
if (!IsEnabled())
return;
UploadAABBTree();
UploadLights();
FGLDebug::PushGroup("ShadowMap");
FGLPostProcessState savedState;
GLRenderer->mBuffers->BindShadowMapFB();
GLRenderer->mShadowMapShader->Bind();
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, mLightList);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, mNodesBuffer);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, mLinesBuffer);
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, 4, 0);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, 0);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, 0);
GLRenderer->mBuffers->BindShadowMapTexture(16);
FGLDebug::PopGroup();
}
bool FShadowMap::ShadowTest(ADynamicLight *light, const DVector3 &pos)
{
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();
mLightToShadowmap.Clear(mLightToShadowmap.CountUsed() * 2); // To do: allow clearing a TMap while building up a reserve
TThinkerIterator<ADynamicLight> it(STAT_DLIGHT);
while (true)
{
ADynamicLight *light = it.Next();
if (!light) break;
mLightToShadowmap[light] = mLights.Size() / 4;
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 (mLights.Size() < 1024 * 4)
mLights.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) * mLights.Size(), &mLights[0], GL_STATIC_DRAW);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, oldBinding);
}
void FShadowMap::UploadAABBTree()
{
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;
}

View file

@ -0,0 +1,60 @@
#pragma once
#include "gl/dynlights/gl_aabbtree.h"
#include "tarray.h"
#include <memory>
class ADynamicLight;
class FShadowMap
{
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);
// 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();
// 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<float> mLights;
// The assigned shadow map index for each light
TMap<ADynamicLight*, int> 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<LevelAABBTree> mAABBTree;
FShadowMap(const FShadowMap &) = delete;
FShadowMap &operator=(FShadowMap &) = delete;
};

View file

@ -740,6 +740,49 @@ 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);
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);
glActiveTexture(activeTex);
glBindFramebuffer(GL_FRAMEBUFFER, frameBufferBinding);
}
//==========================================================================
//
// Makes the scene frame buffer active (multisample, depth, stecil, etc.)

View file

@ -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<GLuint> mEyeTextures;
TArray<GLuint> mEyeFBs;
// Shadow map texture
GLuint mShadowMapTexture = 0;
GLuint mShadowMapFB = 0;
static bool FailedCreate;
static bool BuffersActive;
};

View file

@ -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;
}

View file

@ -6,6 +6,7 @@
#include "vectors.h"
#include "r_renderer.h"
#include "gl/data/gl_matrix.h"
#include "gl/dynlights/gl_shadowmap.h"
struct particle_t;
class FCanvasTexture;
@ -40,6 +41,7 @@ class FPresent3DColumnShader;
class FPresent3DRowShader;
class F2DDrawer;
class FHardwareTexture;
class FShadowMapShader;
inline float DEG2RAD(float deg)
{
@ -122,6 +124,9 @@ public:
FPresent3DCheckerShader *mPresent3dCheckerShader;
FPresent3DColumnShader *mPresent3dColumnShader;
FPresent3DRowShader *mPresent3dRowShader;
FShadowMapShader *mShadowMapShader;
FShadowMap mShadowMap;
FTexture *gllight;
FTexture *glpart2;

View file

@ -950,6 +950,8 @@ void FGLRenderer::RenderView (player_t* player)
TThinkerIterator<ADynamicLight> 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();

View file

@ -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;

View file

@ -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;
@ -270,6 +275,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;
}

View file

@ -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();
}

View file

@ -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

View file

@ -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)

View file

@ -2675,7 +2675,7 @@ GLLIGHTMNU_LIGHTDEFS = "Enable light definitions";
GLLIGHTMNU_CLIPLIGHTS = "Clip lights";
GLLIGHTMNU_LIGHTSPRITES = "Lights affect sprites";
GLLIGHTMNU_LIGHTPARTICLES = "Lights affect particles";
GLLIGHTMNU_LIGHTMATH = "Light quality";
GLLIGHTMNU_LIGHTSHADOWMAP = "Light shadowmaps";
// OpenGL Preferences
GLPREFMNU_TITLE = "OPENGL PREFERENCES";

View file

@ -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"

View file

@ -26,6 +26,7 @@ out vec4 FragNormal;
uniform sampler2D tex;
uniform sampler2D ShadowMap;
vec4 Process(vec4 color);
vec4 ProcessTexel();
@ -132,6 +133,74 @@ float R_DoomLightingEquation(float light)
return lightscale;
}
//===========================================================================
//
// Check if light is in shadow according to its 1D shadow map
//
//===========================================================================
#ifdef SUPPORTS_SHADOWMAPS
float sampleShadowmap(vec2 dir, float v)
{
float u;
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);
}
float dist2 = dot(dir, dir);
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)
{
if (shadowIndex >= 1024.0)
return 1.0; // No shadowmap available for this light
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(ray + normal * x - bias * abs(x), v);
}
return sum / PCF_COUNT;
}
#endif
//===========================================================================
//
// Standard lambertian diffuse light calculation
@ -151,10 +220,14 @@ float diffuseContribution(vec3 lightDirection, vec3 normal)
//
//===========================================================================
float pointLightAttenuation(vec4 lightpos, float attenuate)
float pointLightAttenuation(vec4 lightpos, float lightcolorA)
{
float attenuation = max(lightpos.w - distance(pixelpos.xyz, lightpos.xyz),0.0) / lightpos.w;
if (attenuate == 0.0)
#ifdef SUPPORTS_SHADOWMAPS
float shadowIndex = abs(lightcolorA) - 1.0;
attenuation *= shadowmapAttenuation(lightpos, shadowIndex);
#endif
if (lightcolorA >= 0.0) // Sign bit is the attenuated light flag
{
return attenuation;
}

View file

@ -0,0 +1,162 @@
in vec2 TexCoord;
out vec4 FragColor;
struct GPUNode
{
vec2 aabb_min;
vec2 aabb_max;
int left;
int right;
int line_index;
int padding;
};
struct GPULine
{
vec2 pos;
vec2 delta;
};
layout(std430, binding = 2) buffer LightNodes
{
GPUNode nodes[];
};
layout(std430, binding = 3) buffer LightLines
{
GPULine lines[];
};
layout(std430, binding = 4) buffer LightList
{
vec4 lights[];
};
bool overlapRayAABB(vec2 ray_start2d, vec2 ray_end2d, vec2 aabb_min2d, vec2 aabb_max2d)
{
// To do: simplify test to use a 2D test
vec3 ray_start = vec3(ray_start2d, 0.0);
vec3 ray_end = vec3(ray_end2d, 0.0);
vec3 aabb_min = vec3(aabb_min2d, -1.0);
vec3 aabb_max = vec3(aabb_max2d, 1.0);
vec3 c = (ray_start + ray_end) * 0.5f;
vec3 w = ray_end - c;
vec3 h = (aabb_max - aabb_min) * 0.5f; // aabb.extents();
c -= (aabb_max + aabb_min) * 0.5f; // aabb.center();
vec3 v = abs(w);
if (abs(c.x) > v.x + h.x || abs(c.y) > v.y + h.y || abs(c.z) > v.z + h.z)
return false; // disjoint;
if (abs(c.y * w.z - c.z * w.y) > h.y * v.z + h.z * v.y ||
abs(c.x * w.z - c.z * w.x) > h.x * v.z + h.z * v.x ||
abs(c.x * w.y - c.y * w.x) > h.x * v.y + h.y * v.x)
return false; // disjoint;
return true; // overlap;
}
float intersectRayLine(vec2 ray_start, vec2 ray_end, int line_index, vec2 raydelta, float rayd, float raydist2)
{
const float epsilon = 0.0000001;
GPULine line = lines[line_index];
vec2 raynormal = vec2(raydelta.y, -raydelta.x);
float den = dot(raynormal, line.delta);
if (abs(den) > epsilon)
{
float t_line = (rayd - dot(raynormal, line.pos)) / den;
if (t_line >= 0.0 && t_line <= 1.0)
{
vec2 linehitdelta = line.pos + line.delta * t_line - ray_start;
float t = dot(raydelta, linehitdelta) / raydist2;
return t > 0.0 ? t : 1.0;
}
}
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()
{
int lightIndex = int(gl_FragCoord.y);
vec4 light = lights[lightIndex];
float radius = light.w;
vec2 lightpos = light.xy;
if (radius > 0.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;
float t = rayTest(lightpos, pixelpos);
vec2 delta = (pixelpos - lightpos) * t;
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);
}
}