36011107e3d19de1f6aa07eb7235d0fdc9d869bc
[libs/gl.git] / source / effects / shadowmap.cpp
1 #include <msp/strings/format.h>
2 #include "directionallight.h"
3 #include "error.h"
4 #include "lighting.h"
5 #include "renderer.h"
6 #include "resources.h"
7 #include "shadowmap.h"
8
9 using namespace std;
10
11 namespace Msp {
12 namespace GL {
13
14 ShadowMap::ShadowMap(unsigned w, unsigned h, Renderable &r, const Lighting *l, Renderable &c):
15         Effect(r),
16         width(w),
17         height(h),
18         lighting(l),
19         shadow_caster(c),
20         sampler(Resources::get_global().get<Sampler>("_linear_clamp_shadow.samp"))
21 {
22         depth_buf.storage(DEPTH_COMPONENT32F, width, height, 1);
23         fbo.set_format((DEPTH_ATTACHMENT,DEPTH_COMPONENT32F));
24         fbo.attach(DEPTH_ATTACHMENT, depth_buf, 0);
25
26         set_darkness(1.0f);
27         for(unsigned i=0; i<4; ++i)
28         {
29                 string base = format("shadows[%d]", i);
30                 shdata.uniform(base+".enabled", 0);
31                 shdata.uniform(base+".darkness", 1.0f);
32                 shdata.uniform(base+".shd_world_matrix", Matrix());
33                 shdata.uniform(base+".region", Vector4(0.0f, 0.0f, 1.0f, 1.0f));
34         }
35 }
36
37 ShadowMap::ShadowMap(unsigned s, Renderable &r, const DirectionalLight &l, Renderable &c):
38         ShadowMap(s, s, r, 0, c)
39 {
40         add_light(l, s);
41 }
42
43 ShadowMap::ShadowMap(unsigned w, unsigned h, Renderable &r, const Lighting &l, Renderable &c):
44         ShadowMap(w, h, r, &l, c)
45 { }
46
47 void ShadowMap::add_light(const DirectionalLight &light, unsigned s)
48 {
49         if(!lighting && !lights.empty())
50                 throw invalid_operation("ShadowMap::add_light");
51
52         int index = (lighting ? lighting->find_light_index(light) : 0);
53         if(index<0)
54                 throw invalid_argument("ShadowMap::add_light");
55
56         Rect region(0, 0, s, s);
57         while(1)
58         {
59                 int next_bottom = height;
60                 int next_left = region.left;
61
62                 int top = region.bottom+region.height;
63                 int right = region.left+region.width;
64                 for(const ShadowedLight &l: lights)
65                 {
66                         int l_top = l.region.bottom+l.region.height;
67                         int l_right = l.region.left+l.region.width;
68                         if(l_top>region.bottom)
69                                 next_bottom = min(next_bottom, l_top);
70
71                         if(top>l.region.bottom && region.bottom<l_top && right>l.region.left && region.left<l_right)
72                                 next_left = max(next_left, l_right);
73                 }
74
75                 if(next_left==region.left)
76                         break;
77                 else if(next_left+region.width>width)
78                 {
79                         if(next_bottom+region.height>height)
80                                 throw invalid_operation("ShadowMap::add_light");
81                         region.bottom = next_bottom;
82                         region.left = 0;
83                 }
84                 else
85                         region.left = next_left;
86         }
87
88         lights.emplace_back();
89         ShadowedLight &sl = lights.back();
90         sl.light = &light;
91         sl.index = index;
92         sl.region = region;
93
94         string base = format("shadows[%d]", index);
95         shdata.uniform(base+".enabled", 1);
96         shdata.uniform(base+".darkness", darkness);
97
98         float xf = static_cast<float>(region.left)/width;
99         float yf = static_cast<float>(region.bottom)/height;
100         float wf = static_cast<float>(region.width)/width;
101         float hf = static_cast<float>(region.height)/height;
102         shdata.uniform(base+".region", Vector4(xf, yf, wf, hf));
103
104 #ifdef DEBUG
105         if(!debug_name.empty())
106                 sl.shadow_camera.set_debug_name(format("%s/light%d.camera", debug_name, lights.size()-1));
107 #endif
108 }
109
110 void ShadowMap::set_target(const Vector3 &t, float r)
111 {
112         target = t;
113         radius = r;
114 }
115
116 void ShadowMap::set_darkness(float d)
117 {
118         if(d<0.0f || d>1.0f)
119                 throw invalid_argument("ShadowMap::set_darkness");
120
121         darkness = d;
122         for(const ShadowedLight &l: lights)
123                 shdata.uniform(format("shadows[%d].darkness", l.index), d);
124 }
125
126 void ShadowMap::set_depth_bias(float b)
127 {
128         if(b<0.0f)
129                 throw invalid_argument("ShadowMap::set_depth_bias");
130
131         depth_bias = b;
132 }
133
134 void ShadowMap::setup_frame(Renderer &renderer)
135 {
136         if(rendered)
137                 return;
138
139         rendered = true;
140         renderable.setup_frame(renderer);
141         shadow_caster.setup_frame(renderer);
142
143         for(ShadowedLight &l: lights)
144         {
145                 l.shadow_camera.set_object_matrix(*l.light->get_matrix());
146                 l.shadow_camera.set_position(target);
147                 // TODO support point and spot lights with a frustum projection.
148                 // Omnidirectional lights also need a cube shadow map.
149                 l.shadow_camera.set_orthographic(radius*2, radius*2);
150                 l.shadow_camera.set_depth_clip(-radius, radius);
151
152                 Matrix to_texcoord = Matrix().translate(Vector3(0.5f, 0.5f, 0.5f-depth_bias/l.region.width)).scale(0.5f);
153                 Matrix shadow_matrix = to_texcoord*l.shadow_camera.get_projection_matrix()*l.shadow_camera.get_view_matrix();
154
155                 shdata.uniform(format("shadows[%d].shd_world_matrix", l.index), shadow_matrix);
156         }
157
158         for(ShadowedLight &l: lights)
159         {
160                 Renderer::Push push(renderer);
161                 renderer.set_framebuffer(&fbo);
162                 renderer.set_viewport(&l.region);
163                 renderer.set_scissor(&l.region);
164                 renderer.set_camera(l.shadow_camera);
165
166                 renderer.render(shadow_caster);
167         }
168 }
169
170 void ShadowMap::finish_frame()
171 {
172         if(rendered)
173         {
174                 rendered = false;
175                 renderable.finish_frame();
176         }
177 }
178
179 void ShadowMap::render(Renderer &renderer, Tag tag) const
180 {
181         if(!enabled_passes.count(tag))
182                 return renderer.render(renderable, tag);
183
184         Renderer::Push _push_rend(renderer);
185
186         renderer.set_texture("shadow_map", &depth_buf, &sampler);
187         renderer.add_shader_data(shdata);
188         renderer.render(renderable, tag);
189 }
190
191 void ShadowMap::set_debug_name(const string &name)
192 {
193 #ifdef DEBUG
194         fbo.set_debug_name(name+" [FBO]");
195         for(unsigned i=0; i<lights.size(); ++i)
196                 lights[i].shadow_camera.set_debug_name(format("%s/light%d.camera", name, i));
197         depth_buf.set_debug_name(name+"/depth.tex2d");
198         shdata.set_debug_name(name+" [UBO]");
199 #else
200         (void)name;
201 #endif
202 }
203
204 } // namespace GL
205 } // namespace Msp