Document the algorithm used for generating the 1D shadow maps

This commit is contained in:
Magnus Norddahl 2017-03-08 14:12:59 +01:00
parent d09c3ad305
commit b281a697ce
2 changed files with 49 additions and 0 deletions

View File

@ -32,6 +32,39 @@
#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()
{
UploadAABBTree();

View File

@ -13,27 +13,43 @@ 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) { return mLightToShadowmap[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);
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;