- moved shadowmap to 'common'.
This commit is contained in:
parent
c30165db0d
commit
fde9172ea3
8 changed files with 8 additions and 9 deletions
166
src/common/rendering/hwrenderer/data/hw_aabbtree.cpp
Normal file
166
src/common/rendering/hwrenderer/data/hw_aabbtree.cpp
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
82
src/common/rendering/hwrenderer/data/hw_aabbtree.h
Normal file
82
src/common/rendering/hwrenderer/data/hw_aabbtree.h
Normal 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
|
||||
155
src/common/rendering/hwrenderer/data/hw_shadowmap.cpp
Normal file
155
src/common/rendering/hwrenderer/data/hw_shadowmap.cpp
Normal 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();
|
||||
}
|
||||
|
||||
83
src/common/rendering/hwrenderer/data/hw_shadowmap.h
Normal file
83
src/common/rendering/hwrenderer/data/hw_shadowmap.h
Normal 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;
|
||||
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue