mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-11-10 23:02:08 +00:00
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:
commit
9eae422dab
20 changed files with 994 additions and 5 deletions
|
@ -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
|
||||
|
|
288
src/gl/dynlights/gl_aabbtree.cpp
Normal file
288
src/gl/dynlights/gl_aabbtree.cpp
Normal 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(), ¢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 &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;
|
||||
}
|
59
src/gl/dynlights/gl_aabbtree.h
Normal file
59
src/gl/dynlights/gl_aabbtree.h
Normal 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);
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
206
src/gl/dynlights/gl_shadowmap.cpp
Normal file
206
src/gl/dynlights/gl_shadowmap.cpp
Normal 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;
|
||||
}
|
60
src/gl/dynlights/gl_shadowmap.h
Normal file
60
src/gl/dynlights/gl_shadowmap.h
Normal 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;
|
||||
};
|
|
@ -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.)
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
45
src/gl/shaders/gl_shadowmapshader.cpp
Normal file
45
src/gl/shaders/gl_shadowmapshader.cpp
Normal 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();
|
||||
}
|
15
src/gl/shaders/gl_shadowmapshader.h
Normal file
15
src/gl/shaders/gl_shadowmapshader.h
Normal 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
|
|
@ -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)
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
162
wadsrc/static/shaders/glsl/shadowmap.fp
Normal file
162
wadsrc/static/shaders/glsl/shadowmap.fp
Normal 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);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue