From b281a697ce3666f2c771d3e2f5d070ac573b8f6a Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Wed, 8 Mar 2017 14:12:59 +0100 Subject: [PATCH] Document the algorithm used for generating the 1D shadow maps --- src/gl/dynlights/gl_shadowmap.cpp | 33 +++++++++++++++++++++++++++++++ src/gl/dynlights/gl_shadowmap.h | 16 +++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/src/gl/dynlights/gl_shadowmap.cpp b/src/gl/dynlights/gl_shadowmap.cpp index 8f8b74f44..d43738f85 100644 --- a/src/gl/dynlights/gl_shadowmap.cpp +++ b/src/gl/dynlights/gl_shadowmap.cpp @@ -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(); diff --git a/src/gl/dynlights/gl_shadowmap.h b/src/gl/dynlights/gl_shadowmap.h index b999a550f..da63b37e7 100644 --- a/src/gl/dynlights/gl_shadowmap.h +++ b/src/gl/dynlights/gl_shadowmap.h @@ -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 mLights; + + // The assigned shadow map index for each light TMap 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 mAABBTree; FShadowMap(const FShadowMap &) = delete;