- moved shadowmap to 'common'.

This commit is contained in:
Christoph Oelckers 2020-04-26 20:28:43 +02:00
commit fde9172ea3
8 changed files with 8 additions and 9 deletions

View file

@ -0,0 +1,166 @@
//
//---------------------------------------------------------------------------
// 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 <algorithm>
#include "hw_aabbtree.h"
namespace hwrenderer
{
TArray<int> LevelAABBTree::FindNodePath(unsigned int line, unsigned int node)
{
const AABBTreeNode &n = nodes[node];
if (n.aabb_left > treelines[line].x || n.aabb_right < treelines[line].x ||
n.aabb_top > treelines[line].y || n.aabb_bottom < treelines[line].y)
{
return {};
}
TArray<int> path;
if (n.line_index == -1)
{
path = FindNodePath(line, n.left_node);
if (path.Size() == 0)
path = FindNodePath(line, n.right_node);
if (path.Size())
path.Push(node);
}
else if (n.line_index == (int)line)
{
path.Push(node);
}
return path;
}
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[32];
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 = std::min(IntersectRayLine(ray_start, ray_end, nodes[node_index].line_index, raydelta, rayd, raydist2), hit_fraction);
stack_pos--;
}
else if (stack_pos == 32)
{
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(fabs(w.X), fabs(w.Y), fabs(w.Z));
if (fabs(c.X) > v.X + h.X || fabs(c.Y) > v.Y + h.Y || fabs(c.Z) > v.Z + h.Z)
return false; // disjoint;
if (fabs(c.Y * w.Z - c.Z * w.Y) > h.Y * v.Z + h.Z * v.Y ||
fabs(c.X * w.Z - c.Z * w.X) > h.X * v.Z + h.Z * v.X ||
fabs(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 = treelines[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 (fabs(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;
}
}

View file

@ -0,0 +1,82 @@
#pragma once
#include "tarray.h"
#include "vectors.h"
struct FLevelLocals;
namespace hwrenderer
{
// 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;
};
class LevelAABBTree
{
protected:
// 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> treelines;
int dynamicStartNode = 0;
int dynamicStartLine = 0;
public:
// 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);
const void *Nodes() const { return nodes.Data(); }
const void *Lines() const { return treelines.Data(); }
size_t NodesSize() const { return nodes.Size() * sizeof(AABBTreeNode); }
size_t LinesSize() const { return treelines.Size() * sizeof(AABBTreeLine); }
unsigned int NodesCount() const { return nodes.Size(); }
const void *DynamicNodes() const { return nodes.Data() + dynamicStartNode; }
const void *DynamicLines() const { return treelines.Data() + dynamicStartLine; }
size_t DynamicNodesSize() const { return (nodes.Size() - dynamicStartNode) * sizeof(AABBTreeNode); }
size_t DynamicLinesSize() const { return (treelines.Size() - dynamicStartLine) * sizeof(AABBTreeLine); }
size_t DynamicNodesOffset() const { return dynamicStartNode * sizeof(AABBTreeNode); }
size_t DynamicLinesOffset() const { return dynamicStartLine * sizeof(AABBTreeLine); }
virtual bool Update() = 0;
protected:
TArray<int> FindNodePath(unsigned int line, unsigned int 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);
};
} // namespace

View file

@ -0,0 +1,155 @@
//
//---------------------------------------------------------------------------
// 1D dynamic shadow maps (API independent part)
// 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 "hw_shadowmap.h"
#include "hw_cvars.h"
#include "hw_dynlightdata.h"
#include "buffers.h"
#include "shaderuniforms.h"
#include "hwrenderer/postprocessing/hw_postprocess.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.
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.
*/
cycle_t IShadowMap::UpdateCycles;
int IShadowMap::LightsProcessed;
int IShadowMap::LightsShadowmapped;
CVAR(Bool, gl_light_shadowmap, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
ADD_STAT(shadowmap)
{
FString out;
out.Format("upload=%04.2f ms lights=%d shadowmapped=%d", IShadowMap::UpdateCycles.TimeMS(), IShadowMap::LightsProcessed, IShadowMap::LightsShadowmapped);
return out;
}
CUSTOM_CVAR(Int, gl_shadowmap_quality, 512, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
{
switch (self)
{
case 128:
case 256:
case 512:
case 1024:
break;
default:
self = 128;
break;
}
}
bool IShadowMap::ShadowTest(const DVector3 &lpos, const DVector3 &pos)
{
if (mAABBTree && gl_light_shadowmap)
return mAABBTree->RayTest(lpos, pos) >= 1.0f;
else
return true;
}
bool IShadowMap::PerformUpdate()
{
UpdateCycles.Reset();
LightsProcessed = 0;
LightsShadowmapped = 0;
if (gl_light_shadowmap && (screen->hwcaps & RFL_SHADER_STORAGE_BUFFER) && CollectLights != nullptr)
{
UpdateCycles.Clock();
UploadAABBTree();
UploadLights();
return true;
}
return false;
}
void IShadowMap::UploadLights()
{
mLights.Resize(1024 * 4);
CollectLights();
if (mLightList == nullptr)
mLightList = screen->CreateDataBuffer(LIGHTLIST_BINDINGPOINT, true, false);
mLightList->SetData(sizeof(float) * mLights.Size(), &mLights[0]);
}
void IShadowMap::UploadAABBTree()
{
if (mNewTree)
{
mNewTree = false;
if (!mNodesBuffer)
mNodesBuffer = screen->CreateDataBuffer(LIGHTNODES_BINDINGPOINT, true, false);
mNodesBuffer->SetData(mAABBTree->NodesSize(), mAABBTree->Nodes());
if (!mLinesBuffer)
mLinesBuffer = screen->CreateDataBuffer(LIGHTLINES_BINDINGPOINT, true, false);
mLinesBuffer->SetData(mAABBTree->LinesSize(), mAABBTree->Lines());
}
else if (mAABBTree->Update())
{
mNodesBuffer->SetSubData(mAABBTree->DynamicNodesOffset(), mAABBTree->DynamicNodesSize(), mAABBTree->DynamicNodes());
mLinesBuffer->SetSubData(mAABBTree->DynamicLinesOffset(), mAABBTree->DynamicLinesSize(), mAABBTree->DynamicLines());
}
}
void IShadowMap::Reset()
{
delete mLightList; mLightList = nullptr;
delete mNodesBuffer; mNodesBuffer = nullptr;
delete mLinesBuffer; mLinesBuffer = nullptr;
}
IShadowMap::~IShadowMap()
{
Reset();
}

View file

@ -0,0 +1,83 @@
#pragma once
#include "hw_aabbtree.h"
#include "stats.h"
#include <memory>
class IDataBuffer;
class IShadowMap
{
public:
IShadowMap() { }
virtual ~IShadowMap();
void Reset();
// Test if a world position is in shadow relative to the specified light and returns false if it is
bool ShadowTest(const DVector3 &lpos, const DVector3 &pos);
static cycle_t UpdateCycles;
static int LightsProcessed;
static int LightsShadowmapped;
bool PerformUpdate();
void FinishUpdate()
{
UpdateCycles.Clock();
}
unsigned int NodesCount() const
{
assert(mAABBTree);
return mAABBTree->NodesCount();
}
void SetAABBTree(hwrenderer::LevelAABBTree* tree)
{
mAABBTree = tree;
mNewTree = true;
}
void SetCollectLights(std::function<void()> func)
{
CollectLights = std::move(func);
}
void SetLight(int index, float x, float y, float z, float r)
{
index *= 4;
mLights[index] = x;
mLights[index + 1] = y;
mLights[index + 2] = z;
mLights[index + 3] = r;
}
protected:
// Upload the AABB-tree to the GPU
void UploadAABBTree();
void UploadLights();
// Working buffer for creating the list of lights. Stored here to avoid allocating memory each frame
TArray<float> mLights;
// AABB-tree of the level, used for ray tests, owned by the playsim, not the renderer.
hwrenderer::LevelAABBTree* mAABBTree = nullptr;
bool mNewTree = false;
IShadowMap(const IShadowMap &) = delete;
IShadowMap &operator=(IShadowMap &) = delete;
// OpenGL storage buffer with the list of lights in the shadow map texture
// These buffers need to be accessed by the OpenGL backend directly so that they can be bound.
public:
IDataBuffer *mLightList = nullptr;
// OpenGL storage buffers for the AABB tree
IDataBuffer *mNodesBuffer = nullptr;
IDataBuffer *mLinesBuffer = nullptr;
std::function<void()> CollectLights = nullptr;
};