mirror of
https://github.com/ZDoom/qzdoom.git
synced 2025-02-18 01:52:34 +00:00
- moved gl_spritelight out of gl/. This required a few more changes:
* split gl_shadowmap.cpp into a GL dependent and an API independent part. * gl_drawinfo must be kept around for the HUD sprite because it connects the renderer with the hardware indpendent part of the engine.
This commit is contained in:
parent
819ea8f937
commit
634b3cf413
12 changed files with 256 additions and 193 deletions
|
@ -825,7 +825,6 @@ set( FASTMATH_SOURCES
|
||||||
gl/scene/gl_swscene.cpp
|
gl/scene/gl_swscene.cpp
|
||||||
gl/scene/gl_portal.cpp
|
gl/scene/gl_portal.cpp
|
||||||
gl/scene/gl_walls_draw.cpp
|
gl/scene/gl_walls_draw.cpp
|
||||||
gl/scene/gl_spritelight.cpp
|
|
||||||
gl_load/gl_load.c
|
gl_load/gl_load.c
|
||||||
hwrenderer/dynlights/hw_dynlightdata.cpp
|
hwrenderer/dynlights/hw_dynlightdata.cpp
|
||||||
hwrenderer/scene/hw_fakeflat.cpp
|
hwrenderer/scene/hw_fakeflat.cpp
|
||||||
|
@ -835,6 +834,7 @@ set( FASTMATH_SOURCES
|
||||||
hwrenderer/scene/hw_renderhacks.cpp
|
hwrenderer/scene/hw_renderhacks.cpp
|
||||||
hwrenderer/scene/hw_sky.cpp
|
hwrenderer/scene/hw_sky.cpp
|
||||||
hwrenderer/scene/hw_sprites.cpp
|
hwrenderer/scene/hw_sprites.cpp
|
||||||
|
hwrenderer/scene/hw_spritelight.cpp
|
||||||
hwrenderer/scene/hw_walls.cpp
|
hwrenderer/scene/hw_walls.cpp
|
||||||
hwrenderer/scene/hw_walls_vertex.cpp
|
hwrenderer/scene/hw_walls_vertex.cpp
|
||||||
r_data/models/models.cpp
|
r_data/models/models.cpp
|
||||||
|
@ -1059,6 +1059,7 @@ set (PCH_SOURCES
|
||||||
gl/textures/gl_samplers.cpp
|
gl/textures/gl_samplers.cpp
|
||||||
hwrenderer/data/flatvertices.cpp
|
hwrenderer/data/flatvertices.cpp
|
||||||
hwrenderer/dynlights/hw_aabbtree.cpp
|
hwrenderer/dynlights/hw_aabbtree.cpp
|
||||||
|
hwrenderer/dynlights/hw_shadowmap.cpp
|
||||||
hwrenderer/scene/hw_skydome.cpp
|
hwrenderer/scene/hw_skydome.cpp
|
||||||
hwrenderer/textures/hw_material.cpp
|
hwrenderer/textures/hw_material.cpp
|
||||||
hwrenderer/textures/hw_precache.cpp
|
hwrenderer/textures/hw_precache.cpp
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//
|
//
|
||||||
//---------------------------------------------------------------------------
|
//---------------------------------------------------------------------------
|
||||||
// 1D dynamic shadow maps
|
// 1D dynamic shadow maps (OpenGL dependent part)
|
||||||
// Copyright(C) 2017 Magnus Norddahl
|
// Copyright(C) 2017 Magnus Norddahl
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
//
|
//
|
||||||
|
@ -32,77 +32,6 @@
|
||||||
#include "hwrenderer/dynlights/hw_dynlightdata.h"
|
#include "hwrenderer/dynlights/hw_dynlightdata.h"
|
||||||
#include "stats.h"
|
#include "stats.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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
cycle_t UpdateCycles;
|
|
||||||
int LightsProcessed;
|
|
||||||
int LightsShadowmapped;
|
|
||||||
}
|
|
||||||
|
|
||||||
ADD_STAT(shadowmap)
|
|
||||||
{
|
|
||||||
FString out;
|
|
||||||
out.Format("upload=%04.2f ms lights=%d shadowmapped=%d", UpdateCycles.TimeMS(), LightsProcessed, 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CUSTOM_CVAR (Bool, gl_light_shadowmap, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
|
|
||||||
{
|
|
||||||
if (!self)
|
|
||||||
{
|
|
||||||
// Unset any residual shadow map indices in the light actors.
|
|
||||||
TThinkerIterator<ADynamicLight> it(STAT_DLIGHT);
|
|
||||||
while (auto light = it.Next())
|
|
||||||
{
|
|
||||||
light->mShadowmapIndex = 1024;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void FShadowMap::Update()
|
void FShadowMap::Update()
|
||||||
{
|
{
|
||||||
UpdateCycles.Reset();
|
UpdateCycles.Reset();
|
||||||
|
@ -145,52 +74,9 @@ void FShadowMap::Update()
|
||||||
UpdateCycles.Unclock();
|
UpdateCycles.Unclock();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FShadowMap::ShadowTest(ADynamicLight *light, const DVector3 &pos)
|
|
||||||
{
|
|
||||||
if (light->shadowmapped && light->radius > 0.0 && 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
void FShadowMap::UploadLights()
|
void FShadowMap::UploadLights()
|
||||||
{
|
{
|
||||||
if (mLights.Size() != 1024 * 4) mLights.Resize(1024 * 4);
|
CollectLights();
|
||||||
int lightindex = 0;
|
|
||||||
|
|
||||||
// Todo: this should go through the blockmap in a spiral pattern around the player so that closer lights are preferred.
|
|
||||||
TThinkerIterator<ADynamicLight> it(STAT_DLIGHT);
|
|
||||||
while (auto light = it.Next())
|
|
||||||
{
|
|
||||||
LightsProcessed++;
|
|
||||||
if (light->shadowmapped && lightindex < 1024 * 4)
|
|
||||||
{
|
|
||||||
LightsShadowmapped++;
|
|
||||||
|
|
||||||
light->mShadowmapIndex = lightindex >> 2;
|
|
||||||
|
|
||||||
mLights[lightindex] = light->X();
|
|
||||||
mLights[lightindex+1] = light->Y();
|
|
||||||
mLights[lightindex+2] = light->Z();
|
|
||||||
mLights[lightindex+3] = light->GetRadius();
|
|
||||||
lightindex += 4;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
light->mShadowmapIndex = 1024;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
for (; lightindex < 1024 * 4; lightindex++)
|
|
||||||
{
|
|
||||||
mLights[lightindex] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mLightList == 0)
|
if (mLightList == 0)
|
||||||
glGenBuffers(1, (GLuint*)&mLightList);
|
glGenBuffers(1, (GLuint*)&mLightList);
|
||||||
|
@ -204,16 +90,8 @@ void FShadowMap::UploadLights()
|
||||||
|
|
||||||
void FShadowMap::UploadAABBTree()
|
void FShadowMap::UploadAABBTree()
|
||||||
{
|
{
|
||||||
// Just comparing the level info is not enough. If two MAPINFO-less levels get played after each other,
|
if (!ValidateAABBTree())
|
||||||
// they can both refer to the same default level info.
|
{
|
||||||
if (level.info != mLastLevel && (level.nodes.Size() != mLastNumNodes || level.segs.Size() != mLastNumSegs))
|
|
||||||
Clear();
|
|
||||||
|
|
||||||
if (mAABBTree)
|
|
||||||
return;
|
|
||||||
|
|
||||||
mAABBTree.reset(new hwrenderer::LevelAABBTree());
|
|
||||||
|
|
||||||
int oldBinding = 0;
|
int oldBinding = 0;
|
||||||
glGetIntegerv(GL_SHADER_STORAGE_BUFFER_BINDING, &oldBinding);
|
glGetIntegerv(GL_SHADER_STORAGE_BUFFER_BINDING, &oldBinding);
|
||||||
|
|
||||||
|
@ -226,6 +104,7 @@ void FShadowMap::UploadAABBTree()
|
||||||
glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(hwrenderer::AABBTreeLine) * mAABBTree->lines.Size(), &mAABBTree->lines[0], GL_STATIC_DRAW);
|
glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(hwrenderer::AABBTreeLine) * mAABBTree->lines.Size(), &mAABBTree->lines[0], GL_STATIC_DRAW);
|
||||||
|
|
||||||
glBindBuffer(GL_SHADER_STORAGE_BUFFER, oldBinding);
|
glBindBuffer(GL_SHADER_STORAGE_BUFFER, oldBinding);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FShadowMap::Clear()
|
void FShadowMap::Clear()
|
||||||
|
@ -247,10 +126,4 @@ void FShadowMap::Clear()
|
||||||
glDeleteBuffers(1, (GLuint*)&mLinesBuffer);
|
glDeleteBuffers(1, (GLuint*)&mLinesBuffer);
|
||||||
mLinesBuffer = 0;
|
mLinesBuffer = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
mAABBTree.reset();
|
|
||||||
|
|
||||||
mLastLevel = level.info;
|
|
||||||
mLastNumNodes = level.nodes.Size();
|
|
||||||
mLastNumSegs = level.segs.Size();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,18 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "hwrenderer/dynlights/hw_aabbtree.h"
|
#include "hwrenderer/dynlights/hw_shadowmap.h"
|
||||||
#include "tarray.h"
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
class ADynamicLight;
|
class FShadowMap : public IShadowMap
|
||||||
struct level_info_t;
|
|
||||||
|
|
||||||
class FShadowMap
|
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
FShadowMap() { }
|
|
||||||
~FShadowMap() { Clear(); }
|
~FShadowMap() { Clear(); }
|
||||||
|
|
||||||
// Release resources
|
// Release resources
|
||||||
void Clear();
|
void Clear() override;
|
||||||
|
|
||||||
// Update shadow map texture
|
// Update shadow map texture
|
||||||
void Update();
|
void Update() override;
|
||||||
|
|
||||||
// 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:
|
private:
|
||||||
// Upload the AABB-tree to the GPU
|
// Upload the AABB-tree to the GPU
|
||||||
|
@ -36,21 +24,7 @@ private:
|
||||||
// OpenGL storage buffer with the list of lights in the shadow map texture
|
// OpenGL storage buffer with the list of lights in the shadow map texture
|
||||||
int mLightList = 0;
|
int mLightList = 0;
|
||||||
|
|
||||||
// Working buffer for creating the list of lights. Stored here to avoid allocating memory each frame
|
|
||||||
TArray<float> mLights;
|
|
||||||
|
|
||||||
// OpenGL storage buffers for the AABB tree
|
// OpenGL storage buffers for the AABB tree
|
||||||
int mNodesBuffer = 0;
|
int mNodesBuffer = 0;
|
||||||
int mLinesBuffer = 0;
|
int mLinesBuffer = 0;
|
||||||
|
|
||||||
// Used to detect when a level change requires the AABB tree to be regenerated
|
|
||||||
level_info_t *mLastLevel = nullptr;
|
|
||||||
unsigned mLastNumNodes = 0;
|
|
||||||
unsigned mLastNumSegs = 0;
|
|
||||||
|
|
||||||
// AABB-tree of the level, used for ray tests
|
|
||||||
std::unique_ptr<hwrenderer::LevelAABBTree> mAABBTree;
|
|
||||||
|
|
||||||
FShadowMap(const FShadowMap &) = delete;
|
|
||||||
FShadowMap &operator=(FShadowMap &) = delete;
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -256,6 +256,7 @@ void GLSceneDrawer::CreateScene()
|
||||||
gl_drawinfo->mAngles = GLRenderer->mAngles;
|
gl_drawinfo->mAngles = GLRenderer->mAngles;
|
||||||
gl_drawinfo->mViewVector = GLRenderer->mViewVector;
|
gl_drawinfo->mViewVector = GLRenderer->mViewVector;
|
||||||
gl_drawinfo->mViewActor = GLRenderer->mViewActor;
|
gl_drawinfo->mViewActor = GLRenderer->mViewActor;
|
||||||
|
gl_drawinfo->mShadowMap = &GLRenderer->mShadowMap;
|
||||||
|
|
||||||
RenderBSPNode (level.HeadNode());
|
RenderBSPNode (level.HeadNode());
|
||||||
if (GLRenderer->mCurrentPortal != NULL) GLRenderer->mCurrentPortal->RenderAttached();
|
if (GLRenderer->mCurrentPortal != NULL) GLRenderer->mCurrentPortal->RenderAttached();
|
||||||
|
@ -581,7 +582,6 @@ void GLSceneDrawer::DrawEndScene2D(sector_t * viewsector)
|
||||||
|
|
||||||
void GLSceneDrawer::ProcessScene(bool toscreen)
|
void GLSceneDrawer::ProcessScene(bool toscreen)
|
||||||
{
|
{
|
||||||
FDrawInfo::StartDrawInfo(this);
|
|
||||||
iter_dlightf = iter_dlight = draw_dlight = draw_dlightf = 0;
|
iter_dlightf = iter_dlight = draw_dlight = draw_dlightf = 0;
|
||||||
GLPortal::BeginScene();
|
GLPortal::BeginScene();
|
||||||
|
|
||||||
|
@ -590,7 +590,6 @@ void GLSceneDrawer::ProcessScene(bool toscreen)
|
||||||
CurrentMapSections.Zero();
|
CurrentMapSections.Zero();
|
||||||
CurrentMapSections.Set(mapsection);
|
CurrentMapSections.Set(mapsection);
|
||||||
DrawScene(toscreen ? DM_MAINVIEW : DM_OFFSCREEN);
|
DrawScene(toscreen ? DM_MAINVIEW : DM_OFFSCREEN);
|
||||||
FDrawInfo::EndDrawInfo();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -714,8 +713,11 @@ sector_t * GLSceneDrawer::RenderViewpoint (AActor * camera, GL_IRECT * bounds, f
|
||||||
SetViewMatrix(r_viewpoint.Pos.X, r_viewpoint.Pos.Y, r_viewpoint.Pos.Z, false, false);
|
SetViewMatrix(r_viewpoint.Pos.X, r_viewpoint.Pos.Y, r_viewpoint.Pos.Z, false, false);
|
||||||
gl_RenderState.ApplyMatrices();
|
gl_RenderState.ApplyMatrices();
|
||||||
|
|
||||||
|
FDrawInfo::StartDrawInfo(this);
|
||||||
ProcessScene(toscreen);
|
ProcessScene(toscreen);
|
||||||
if (mainview && toscreen) EndDrawScene(lviewsector); // do not call this for camera textures.
|
if (mainview && toscreen) EndDrawScene(lviewsector); // do not call this for camera textures.
|
||||||
|
FDrawInfo::EndDrawInfo();
|
||||||
|
|
||||||
if (mainview && FGLRenderBuffers::IsEnabled())
|
if (mainview && FGLRenderBuffers::IsEnabled())
|
||||||
{
|
{
|
||||||
GLRenderer->PostProcessScene(FixedColormap, [&]() { if (gl_bloom && FixedColormap == CM_DEFAULT) DrawEndScene2D(lviewsector); });
|
GLRenderer->PostProcessScene(FixedColormap, [&]() { if (gl_bloom && FixedColormap == CM_DEFAULT) DrawEndScene2D(lviewsector); });
|
||||||
|
|
|
@ -69,7 +69,7 @@ int gl_SetDynModelLight(AActor *self, int dynlightindex)
|
||||||
if (gl.legacyMode)
|
if (gl.legacyMode)
|
||||||
{
|
{
|
||||||
float out[3];
|
float out[3];
|
||||||
hw_GetDynSpriteLight(self, nullptr, out);
|
gl_drawinfo->GetDynSpriteLight(self, nullptr, out);
|
||||||
gl_RenderState.SetDynLight(out[0], out[1], out[2]);
|
gl_RenderState.SetDynLight(out[0], out[1], out[2]);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -198,7 +198,7 @@ void FDrawInfo::DrawSprite(GLSprite *sprite, int pass)
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
float out[3];
|
float out[3];
|
||||||
hw_GetDynSpriteLight(gl_light_sprites ? sprite->actor : nullptr, gl_light_particles ? sprite->particle : nullptr, out);
|
GetDynSpriteLight(gl_light_sprites ? sprite->actor : nullptr, gl_light_particles ? sprite->particle : nullptr, out);
|
||||||
gl_RenderState.SetDynLight(out[0], out[1], out[2]);
|
gl_RenderState.SetDynLight(out[0], out[1], out[2]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -491,7 +491,7 @@ void FDrawInfo::DrawDecal(GLDecal *gldecal)
|
||||||
double x, y;
|
double x, y;
|
||||||
float out[3];
|
float out[3];
|
||||||
decal->GetXY(seg->sidedef, x, y);
|
decal->GetXY(seg->sidedef, x, y);
|
||||||
hw_GetDynSpriteLight(nullptr, x, y, gldecal->zcenter, wall->sub, out);
|
GetDynSpriteLight(nullptr, x, y, gldecal->zcenter, wall->sub, out);
|
||||||
gl_RenderState.SetDynLight(out[0], out[1], out[2]);
|
gl_RenderState.SetDynLight(out[0], out[1], out[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -457,7 +457,7 @@ void GLSceneDrawer::DrawPlayerSprites(sector_t * viewsector, bool hudModelStep)
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
float out[3];
|
float out[3];
|
||||||
hw_GetDynSpriteLight(playermo, nullptr, out);
|
gl_drawinfo->GetDynSpriteLight(playermo, nullptr, out);
|
||||||
gl_RenderState.SetDynLight(out[0], out[1], out[2]);
|
gl_RenderState.SetDynLight(out[0], out[1], out[2]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
162
src/hwrenderer/dynlights/hw_shadowmap.cpp
Normal file
162
src/hwrenderer/dynlights/hw_shadowmap.cpp
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// 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 "hwrenderer/dynlights/hw_shadowmap.h"
|
||||||
|
#include "hwrenderer/utility/hw_cvars.h"
|
||||||
|
#include "hwrenderer/dynlights/hw_dynlightdata.h"
|
||||||
|
#include "stats.h"
|
||||||
|
#include "g_levellocals.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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR (Bool, gl_light_shadowmap, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
|
||||||
|
{
|
||||||
|
if (!self)
|
||||||
|
{
|
||||||
|
// Unset any residual shadow map indices in the light actors.
|
||||||
|
TThinkerIterator<ADynamicLight> it(STAT_DLIGHT);
|
||||||
|
while (auto light = it.Next())
|
||||||
|
{
|
||||||
|
light->mShadowmapIndex = 1024;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IShadowMap::ShadowTest(ADynamicLight *light, const DVector3 &pos)
|
||||||
|
{
|
||||||
|
if (light->shadowmapped && light->radius > 0.0 && IsEnabled() && mAABBTree)
|
||||||
|
return mAABBTree->RayTest(light->Pos(), pos) >= 1.0f;
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IShadowMap::IsEnabled() const
|
||||||
|
{
|
||||||
|
return gl_light_shadowmap && (screen->hwcaps & RFL_SHADER_STORAGE_BUFFER);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IShadowMap::CollectLights()
|
||||||
|
{
|
||||||
|
if (mLights.Size() != 1024 * 4) mLights.Resize(1024 * 4);
|
||||||
|
int lightindex = 0;
|
||||||
|
|
||||||
|
// Todo: this should go through the blockmap in a spiral pattern around the player so that closer lights are preferred.
|
||||||
|
TThinkerIterator<ADynamicLight> it(STAT_DLIGHT);
|
||||||
|
while (auto light = it.Next())
|
||||||
|
{
|
||||||
|
LightsProcessed++;
|
||||||
|
if (light->shadowmapped && light->IsActive() && lightindex < 1024 * 4)
|
||||||
|
{
|
||||||
|
LightsShadowmapped++;
|
||||||
|
|
||||||
|
light->mShadowmapIndex = lightindex >> 2;
|
||||||
|
|
||||||
|
mLights[lightindex] = (float)light->X();
|
||||||
|
mLights[lightindex+1] = (float)light->Y();
|
||||||
|
mLights[lightindex+2] = (float)light->Z();
|
||||||
|
mLights[lightindex+3] = light->GetRadius();
|
||||||
|
lightindex += 4;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
light->mShadowmapIndex = 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; lightindex < 1024 * 4; lightindex++)
|
||||||
|
{
|
||||||
|
mLights[lightindex] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IShadowMap::ValidateAABBTree()
|
||||||
|
{
|
||||||
|
// Just comparing the level info is not enough. If two MAPINFO-less levels get played after each other,
|
||||||
|
// they can both refer to the same default level info.
|
||||||
|
if (level.info != mLastLevel && (level.nodes.Size() != mLastNumNodes || level.segs.Size() != mLastNumSegs))
|
||||||
|
{
|
||||||
|
mAABBTree.reset();
|
||||||
|
|
||||||
|
mLastLevel = level.info;
|
||||||
|
mLastNumNodes = level.nodes.Size();
|
||||||
|
mLastNumSegs = level.segs.Size();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mAABBTree)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
mAABBTree.reset(new hwrenderer::LevelAABBTree());
|
||||||
|
return false;
|
||||||
|
}
|
51
src/hwrenderer/dynlights/hw_shadowmap.h
Normal file
51
src/hwrenderer/dynlights/hw_shadowmap.h
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "hw_aabbtree.h"
|
||||||
|
#include "tarray.h"
|
||||||
|
#include "stats.h"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
class ADynamicLight;
|
||||||
|
struct level_info_t;
|
||||||
|
|
||||||
|
class IShadowMap
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IShadowMap() { }
|
||||||
|
virtual ~IShadowMap() { }
|
||||||
|
|
||||||
|
// Release resources
|
||||||
|
virtual void Clear() = 0;
|
||||||
|
|
||||||
|
// Update shadow map texture
|
||||||
|
virtual void Update() = 0;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
static cycle_t UpdateCycles;
|
||||||
|
static int LightsProcessed;
|
||||||
|
static int LightsShadowmapped;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void CollectLights();
|
||||||
|
bool ValidateAABBTree();
|
||||||
|
|
||||||
|
// Working buffer for creating the list of lights. Stored here to avoid allocating memory each frame
|
||||||
|
TArray<float> mLights;
|
||||||
|
|
||||||
|
// Used to detect when a level change requires the AABB tree to be regenerated
|
||||||
|
level_info_t *mLastLevel = nullptr;
|
||||||
|
unsigned mLastNumNodes = 0;
|
||||||
|
unsigned mLastNumSegs = 0;
|
||||||
|
|
||||||
|
// AABB-tree of the level, used for ray tests
|
||||||
|
std::unique_ptr<hwrenderer::LevelAABBTree> mAABBTree;
|
||||||
|
|
||||||
|
IShadowMap(const IShadowMap &) = delete;
|
||||||
|
IShadowMap &operator=(IShadowMap &) = delete;
|
||||||
|
};
|
|
@ -10,6 +10,8 @@ class GLWall;
|
||||||
class GLFlat;
|
class GLFlat;
|
||||||
class GLSprite;
|
class GLSprite;
|
||||||
struct GLDecal;
|
struct GLDecal;
|
||||||
|
class IShadowMap;
|
||||||
|
struct particle_t;
|
||||||
|
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
//
|
//
|
||||||
|
@ -78,6 +80,7 @@ struct HWDrawInfo
|
||||||
FRotator mAngles;
|
FRotator mAngles;
|
||||||
FVector2 mViewVector;
|
FVector2 mViewVector;
|
||||||
AActor *mViewActor;
|
AActor *mViewActor;
|
||||||
|
IShadowMap *mShadowMap;
|
||||||
|
|
||||||
TArray<MissingTextureInfo> MissingUpperTextures;
|
TArray<MissingTextureInfo> MissingUpperTextures;
|
||||||
TArray<MissingTextureInfo> MissingLowerTextures;
|
TArray<MissingTextureInfo> MissingLowerTextures;
|
||||||
|
@ -137,6 +140,10 @@ public:
|
||||||
void AddOtherFloorPlane(int sector, gl_subsectorrendernode * node);
|
void AddOtherFloorPlane(int sector, gl_subsectorrendernode * node);
|
||||||
void AddOtherCeilingPlane(int sector, gl_subsectorrendernode * node);
|
void AddOtherCeilingPlane(int sector, gl_subsectorrendernode * node);
|
||||||
|
|
||||||
|
void GetDynSpriteLight(AActor *self, float x, float y, float z, subsector_t * subsec, float *out);
|
||||||
|
void GetDynSpriteLight(AActor *thing, particle_t *particle, float *out);
|
||||||
|
|
||||||
|
|
||||||
virtual void FloodUpperGap(seg_t * seg) = 0;
|
virtual void FloodUpperGap(seg_t * seg) = 0;
|
||||||
virtual void FloodLowerGap(seg_t * seg) = 0;
|
virtual void FloodLowerGap(seg_t * seg) = 0;
|
||||||
virtual void ProcessLowerMinisegs(TArray<seg_t *> &lowersegs) = 0;
|
virtual void ProcessLowerMinisegs(TArray<seg_t *> &lowersegs) = 0;
|
||||||
|
|
|
@ -432,8 +432,6 @@ inline float Dist2(float x1,float y1,float x2,float y2)
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hw_SetPlaneTextureRotation(const GLSectorPlane * secplane, FMaterial * gltexture, VSMatrix &mat);
|
bool hw_SetPlaneTextureRotation(const GLSectorPlane * secplane, FMaterial * gltexture, VSMatrix &mat);
|
||||||
void hw_GetDynSpriteLight(AActor *self, float x, float y, float z, subsector_t *subsec, float *out);
|
|
||||||
void hw_GetDynSpriteLight(AActor *actor, particle_t *particle, float *out);
|
|
||||||
void hw_GetDynModelLight(AActor *self, FDynLightData &modellightdata);
|
void hw_GetDynModelLight(AActor *self, FDynLightData &modellightdata);
|
||||||
|
|
||||||
extern const float LARGE_VALUE;
|
extern const float LARGE_VALUE;
|
||||||
|
|
|
@ -33,13 +33,8 @@
|
||||||
#include "g_levellocals.h"
|
#include "g_levellocals.h"
|
||||||
#include "actorinlines.h"
|
#include "actorinlines.h"
|
||||||
#include "hwrenderer/dynlights/hw_dynlightdata.h"
|
#include "hwrenderer/dynlights/hw_dynlightdata.h"
|
||||||
|
#include "hwrenderer/dynlights/hw_shadowmap.h"
|
||||||
#include "gl/renderer/gl_renderer.h"
|
#include "hwrenderer/scene/hw_drawinfo.h"
|
||||||
#include "gl/renderer/gl_lightdata.h"
|
|
||||||
#include "gl/scene/gl_drawinfo.h"
|
|
||||||
#include "gl/shaders/gl_shader.h"
|
|
||||||
#include "gl/dynlights/gl_lightbuffer.h"
|
|
||||||
|
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
T smoothstep(const T edge0, const T edge1, const T x)
|
T smoothstep(const T edge0, const T edge1, const T x)
|
||||||
|
@ -54,7 +49,7 @@ T smoothstep(const T edge0, const T edge1, const T x)
|
||||||
//
|
//
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
|
|
||||||
void hw_GetDynSpriteLight(AActor *self, float x, float y, float z, subsector_t * subsec, float *out)
|
void HWDrawInfo::GetDynSpriteLight(AActor *self, float x, float y, float z, subsector_t * subsec, float *out)
|
||||||
{
|
{
|
||||||
ADynamicLight *light;
|
ADynamicLight *light;
|
||||||
float frac, lr, lg, lb;
|
float frac, lr, lg, lb;
|
||||||
|
@ -109,7 +104,7 @@ void hw_GetDynSpriteLight(AActor *self, float x, float y, float z, subsector_t *
|
||||||
frac *= (float)smoothstep(light->SpotOuterAngle.Cos(), light->SpotInnerAngle.Cos(), cosDir);
|
frac *= (float)smoothstep(light->SpotOuterAngle.Cos(), light->SpotInnerAngle.Cos(), cosDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frac > 0 && GLRenderer->mShadowMap.ShadowTest(light, { x, y, z }))
|
if (frac > 0 && (!light->shadowmapped || mShadowMap->ShadowTest(light, { x, y, z })))
|
||||||
{
|
{
|
||||||
lr = light->GetRed() / 255.0f;
|
lr = light->GetRed() / 255.0f;
|
||||||
lg = light->GetGreen() / 255.0f;
|
lg = light->GetGreen() / 255.0f;
|
||||||
|
@ -133,15 +128,15 @@ void hw_GetDynSpriteLight(AActor *self, float x, float y, float z, subsector_t *
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void hw_GetDynSpriteLight(AActor *thing, particle_t *particle, float *out)
|
void HWDrawInfo::GetDynSpriteLight(AActor *thing, particle_t *particle, float *out)
|
||||||
{
|
{
|
||||||
if (thing != NULL)
|
if (thing != NULL)
|
||||||
{
|
{
|
||||||
hw_GetDynSpriteLight(thing, thing->X(), thing->Y(), thing->Center(), thing->subsector, out);
|
GetDynSpriteLight(thing, thing->X(), thing->Y(), thing->Center(), thing->subsector, out);
|
||||||
}
|
}
|
||||||
else if (particle != NULL)
|
else if (particle != NULL)
|
||||||
{
|
{
|
||||||
hw_GetDynSpriteLight(NULL, particle->Pos.X, particle->Pos.Y, particle->Pos.Z, particle->subsector, out);
|
GetDynSpriteLight(NULL, particle->Pos.X, particle->Pos.Y, particle->Pos.Z, particle->subsector, out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue