]> git.tdb.fi Git - libs/gl.git/commitdiff
Refactor ShadowMap to support multiple lights
authorMikko Rasa <tdb@tdb.fi>
Mon, 4 Oct 2021 17:09:08 +0000 (20:09 +0300)
committerMikko Rasa <tdb@tdb.fi>
Mon, 4 Oct 2021 17:42:42 +0000 (20:42 +0300)
shaderlib/common.glsl
shaderlib/shadow.glsl
source/effects/shadowmap.cpp
source/effects/shadowmap.h
source/materials/lighting.cpp
source/materials/lighting.h

index b393b2a0daf2d7e0398a348b60eb4d9753ad2f47..dde09c12cff45bca1a4a60fe95d6a37305404ef8 100644 (file)
@@ -57,8 +57,6 @@ void standard_transform()
                for(int i=0; i<max_clip_planes; ++i)
                        gl_ClipDistance[i] = dot(world_vertex, clip_planes[i].equation);
        }
-
-       shadow_transform(world_vertex);
 }
 
 virtual void custom_transform()
index 65b003f43bcdef2295678d88624d45a5ed7089d2..c26a1ca3782039cbb3ba1e6261abeace13c78859 100644 (file)
@@ -1,30 +1,37 @@
 import msp_interface;
 
-uniform ShadowMap
+struct ShadowParameters
 {
-       float shadow_darkness;
+       int enabled;
+       float darkness;
        mat4 shd_world_matrix;
+       vec4 region;
+};
+
+uniform ShadowMap
+{
+       ShadowParameters shadows[max_lights];
 };
 
 uniform sampler2DShadow shadow_map;
 
 layout(constant_id=auto) const bool use_shadow_map = false;
 
-#pragma MSP stage(vertex)
-void shadow_transform(vec4 world_vertex)
-{
-       out vec3 shadow_coord = (shd_world_matrix*world_vertex).xyz;
-}
-
 #pragma MSP stage(fragment)
 virtual float get_shadow_factor(int index)
 {
        if(use_shadow_map)
        {
-               if(index>0 || shadow_coord.x<0 || shadow_coord.x>1 || shadow_coord.y<0 || shadow_coord.y>1)
+               if(shadows[index].enabled==0)
                        return 1.0;
-               float shadow_sample = texture(shadow_map, shadow_coord);
-               return mix(1.0, shadow_sample, shadow_darkness);
+
+               vec3 shadow_coord = (shadows[index].shd_world_matrix*world_vertex).xyz;
+               if(shadow_coord.x<0 || shadow_coord.x>1 || shadow_coord.y<0 || shadow_coord.y>1)
+                       return 1.0;
+
+               vec4 region = shadows[index].region;
+               float shadow_sample = texture(shadow_map, shadow_coord*vec3(region.zw, 1.0)+vec3(region.xy, 0.0));
+               return mix(1.0, shadow_sample, shadows[index].darkness);
        }
        else
                return 1.0;
index 39cf3c450a484f7a909f01bfbbd8c7ef74bb475c..49dfa36de0bb636d101900dc78e47ca42f03f723 100644 (file)
@@ -1,4 +1,7 @@
+#include <msp/strings/format.h>
+#include "error.h"
 #include "light.h"
+#include "lighting.h"
 #include "renderer.h"
 #include "resources.h"
 #include "shadowmap.h"
@@ -8,22 +11,104 @@ using namespace std;
 namespace Msp {
 namespace GL {
 
-ShadowMap::ShadowMap(unsigned s, Renderable &r, const Light &l, Renderable &c):
+ShadowMap::ShadowMap(unsigned w, unsigned h, Renderable &r, const Lighting *l, Renderable &c):
        Effect(r),
-       size(s),
-       light(l),
+       width(w),
+       height(h),
+       lighting(l),
        shadow_caster(c),
        sampler(Resources::get_global().get<Sampler>("_linear_clamp_shadow.samp")),
        radius(1),
        depth_bias(4),
+       darkness(1.0f),
        rendered(false)
 {
-       depth_buf.storage(DEPTH_COMPONENT32F, size, size, 1);
+       depth_buf.storage(DEPTH_COMPONENT32F, width, height, 1);
        fbo.set_format((DEPTH_ATTACHMENT,DEPTH_COMPONENT32F));
        fbo.attach(DEPTH_ATTACHMENT, depth_buf, 0);
 
        set_darkness(1.0f);
-       shdata.uniform("shd_world_matrix", Matrix());
+       for(unsigned i=0; i<4; ++i)
+       {
+               string base = format("shadows[%d]", i);
+               shdata.uniform(base+".enabled", 0);
+               shdata.uniform(base+".darkness", 1.0f);
+               shdata.uniform(base+".shd_world_matrix", Matrix());
+               shdata.uniform(base+".region", Vector4(0.0f, 0.0f, 1.0f, 1.0f));
+       }
+}
+
+ShadowMap::ShadowMap(unsigned s, Renderable &r, const Light &l, Renderable &c):
+       ShadowMap(s, s, r, 0, c)
+{
+       add_light(l, s);
+}
+
+ShadowMap::ShadowMap(unsigned w, unsigned h, Renderable &r, const Lighting &l, Renderable &c):
+       ShadowMap(w, h, r, &l, c)
+{ }
+
+void ShadowMap::add_light(const Light &light, unsigned s)
+{
+       if(!lighting && !lights.empty())
+               throw invalid_operation("ShadowMap::add_light");
+
+       int index = (lighting ? lighting->find_light_index(light) : 0);
+       if(index<0)
+               throw invalid_argument("ShadowMap::add_light");
+
+       Rect region(0, 0, s, s);
+       while(1)
+       {
+               int next_bottom = height;
+               int next_left = region.left;
+
+               int top = region.bottom+region.height;
+               int right = region.left+region.width;
+               for(const ShadowedLight &l: lights)
+               {
+                       int l_top = l.region.bottom+l.region.height;
+                       int l_right = l.region.left+l.region.width;
+                       if(l_top>region.bottom)
+                               next_bottom = min(next_bottom, l_top);
+
+                       if(top>l.region.bottom && region.bottom<l_top && right>l.region.left && region.left<l_right)
+                               next_left = max(next_left, l_right);
+               }
+
+               if(next_left==region.left)
+                       break;
+               else if(next_left+region.width>width)
+               {
+                       if(next_bottom+region.height>height)
+                               throw invalid_operation("ShadowMap::add_light");
+                       region.bottom = next_bottom;
+                       region.left = 0;
+               }
+               else
+                       region.left = next_left;
+       }
+
+       lights.emplace_back();
+       ShadowedLight &sl = lights.back();
+       sl.light = &light;
+       sl.index = index;
+       sl.region = region;
+
+       string base = format("shadows[%d]", index);
+       shdata.uniform(base+".enabled", 1);
+       shdata.uniform(base+".darkness", darkness);
+
+       float xf = static_cast<float>(region.left)/width;
+       float yf = static_cast<float>(region.bottom)/height;
+       float wf = static_cast<float>(region.width)/width;
+       float hf = static_cast<float>(region.height)/height;
+       shdata.uniform(base+".region", Vector4(xf, yf, wf, hf));
+
+#ifdef DEBUG
+       if(!debug_name.empty())
+               sl.shadow_camera.set_debug_name(format("%s/light%d.camera", debug_name, lights.size()-1));
+#endif
 }
 
 void ShadowMap::set_target(const Vector3 &t, float r)
@@ -37,7 +122,9 @@ void ShadowMap::set_darkness(float d)
        if(d<0.0f || d>1.0f)
                throw invalid_argument("ShadowMap::set_darkness");
 
-       shdata.uniform("shadow_darkness", d);
+       darkness = d;
+       for(const ShadowedLight &l: lights)
+               shdata.uniform(format("shadows[%d].darkness", l.index), d);
 }
 
 void ShadowMap::set_depth_bias(float b)
@@ -57,25 +144,33 @@ void ShadowMap::setup_frame(Renderer &renderer)
        renderable.setup_frame(renderer);
        shadow_caster.setup_frame(renderer);
 
-       shadow_camera.set_object_matrix(*light.get_matrix());
-       shadow_camera.set_position(target);
-       // TODO support point and spot lights with a frustum projection.
-       // Omnidirectional lights also need a cube shadow map.
-       shadow_camera.set_orthographic(radius*2, radius*2);
-       shadow_camera.set_depth_clip(-radius, radius);
-
-       shadow_matrix = shadow_camera.get_object_matrix();
-       shadow_matrix.scale(radius*2, radius*2, -radius*2);
-       shadow_matrix.translate(-0.5, -0.5, depth_bias/size-0.5);
-       shadow_matrix.invert();
-
-       shdata.uniform("shd_world_matrix", shadow_matrix);
+       for(ShadowedLight &l: lights)
+       {
+               l.shadow_camera.set_object_matrix(*l.light->get_matrix());
+               l.shadow_camera.set_position(target);
+               // TODO support point and spot lights with a frustum projection.
+               // Omnidirectional lights also need a cube shadow map.
+               l.shadow_camera.set_orthographic(radius*2, radius*2);
+               l.shadow_camera.set_depth_clip(-radius, radius);
+
+               Matrix shadow_matrix = l.shadow_camera.get_object_matrix();
+               shadow_matrix.scale(radius*2, radius*2, -radius*2);
+               shadow_matrix.translate(-0.5, -0.5, depth_bias/l.region.width-0.5);
+               shadow_matrix.invert();
+
+               shdata.uniform(format("shadows[%d].shd_world_matrix", l.index), shadow_matrix);
+       }
 
-       Renderer::Push push(renderer);
-       renderer.set_framebuffer(&fbo);
-       renderer.set_camera(shadow_camera);
+       for(ShadowedLight &l: lights)
+       {
+               Renderer::Push push(renderer);
+               renderer.set_framebuffer(&fbo);
+               renderer.set_viewport(&l.region);
+               renderer.set_scissor(&l.region);
+               renderer.set_camera(l.shadow_camera);
 
-       renderer.render(shadow_caster);
+               renderer.render(shadow_caster);
+       }
 }
 
 void ShadowMap::finish_frame()
@@ -103,7 +198,8 @@ void ShadowMap::set_debug_name(const string &name)
 {
 #ifdef DEBUG
        fbo.set_debug_name(name+" [FBO]");
-       shadow_camera.set_debug_name(name+".camera");
+       for(unsigned i=0; i<lights.size(); ++i)
+               lights[i].shadow_camera.set_debug_name(format("%s/light%d.camera", name, i));
        depth_buf.set_debug_name(name+"/depth.tex2d");
        shdata.set_debug_name(name+" [UBO]");
 #else
index 67103b86f8abfe11ab56ead2ea801759c5138b1c..c3c7bd55a7f53430937cce8faf6f6955b220f61d 100644 (file)
@@ -5,6 +5,7 @@
 #include "effect.h"
 #include "framebuffer.h"
 #include "programdata.h"
+#include "rect.h"
 #include "texture2d.h"
 #include "vector.h"
 
@@ -22,22 +23,36 @@ texture coordinate generation to determine whether each fragment is lit.
 class ShadowMap: public Effect
 {
 private:
-       unsigned size;
-       const Light &light;
+       struct ShadowedLight
+       {
+               const Light *light;
+               unsigned index;
+               Rect region;
+               Camera shadow_camera;
+       };
+
+       unsigned width;
+       unsigned height;
+       const Lighting *lighting;
+       std::vector<ShadowedLight> lights;
        Renderable &shadow_caster;
        Framebuffer fbo;
-       Camera shadow_camera;
-       Matrix shadow_matrix;
        Texture2D depth_buf;
        const Sampler &sampler;
        Vector3 target;
        float radius;
        float depth_bias;
+       float darkness;
        ProgramData shdata;
        bool rendered;
+       std::string debug_name;
 
+       ShadowMap(unsigned, unsigned, Renderable &, const Lighting *, Renderable &);
 public:
        ShadowMap(unsigned, Renderable &, const Light &, Renderable &);
+       ShadowMap(unsigned, unsigned, Renderable &, const Lighting &, Renderable &);
+
+       void add_light(const Light &, unsigned);
 
        /** Sets the ShadowMap target point and radius.  The transformation matrix is
        computed so that a sphere with the specified parameters will be completely
@@ -56,7 +71,6 @@ public:
        void set_depth_bias(float);
 
        const Texture2D &get_depth_texture() const { return depth_buf; }
-       const Matrix &get_shadow_matrix() const { return shadow_matrix; }
 
        virtual void setup_frame(Renderer &);
        virtual void finish_frame();
index cd04d1b4af153a981973c75e7eb8ff642d8f7545..113735040ed22ccfa50ea3f71f2448249a857b16 100644 (file)
@@ -69,6 +69,12 @@ void Lighting::detach(const Light &l)
        }
 }
 
+int Lighting::find_light_index(const Light &l) const
+{
+       auto i = find_member(lights, &l, &AttachedLight::light);
+       return (i!=lights.end() ? i-lights.begin() : -1);
+}
+
 const ProgramData &Lighting::get_shader_data() const
 {
        for(unsigned i=0; i<lights.size(); ++i)
index d4e6b5942cc09d660857d661b2b8bb700e4d71e4..9dc93b240c7f0042349a0d0732c6a51251b3a92b 100644 (file)
@@ -78,6 +78,8 @@ public:
        /** Detaches a light source.  If the light was not attached, does nothing. */
        void detach(const Light &);
 
+       int find_light_index(const Light &) const;
+
        const ProgramData &get_shader_data() const;
 
        void set_debug_name(const std::string &);