Line data Source code
1 : /**
2 : * @file csm.cpp
3 : * @brief Cascaded shadow maps for sunlight rendering
4 : *
5 : * The cascaded shadow maps are used to provide levels of detail for sunlight
6 : * (nearer areas get higher resolution shadow maps, and farther areas are
7 : * covered by lower quality shadow maps). The cascading shadow maps go into the
8 : * shadow atlas and are treated the same as any other light (though their size
9 : * is typically a sizable portion of the atlas space)
10 : */
11 : #include "../libprimis-headers/cube.h"
12 : #include "../../shared/geomexts.h"
13 : #include "../../shared/glexts.h"
14 :
15 : #include "csm.h"
16 : #include "octarender.h"
17 : #include "rendergl.h"
18 : #include "renderlights.h"
19 : #include "shaderparam.h"
20 : #include "texture.h"
21 :
22 : #include "interface/cs.h"
23 :
24 : #include "world/light.h"
25 :
26 : cascadedshadowmap csm;
27 :
28 : //====================== cascaded shadow map object ============================//
29 :
30 2 : cascadedshadowmap::cascadedshadowmap() : csmmaxsize(768), csmnearplane(1),
31 1 : csmfarplane(1024), csmsplits(3), csmcull(true), csmshadowmap(true),
32 1 : csmsplitweight(0.75f), csmpradiustweak(1.f), csmdepthrange(1024.f),
33 1 : csmdepthmargin(0.1f), csmbias(1e-4f), csmbias2(2e-4f),
34 1 : csmpolyfactor(2), csmpolyfactor2(3), csmpolyoffset(0), csmpolyoffset2(0)
35 : {
36 1 : }
37 :
38 1 : bool cascadedshadowmap::setcsmproperty(int index, float value)
39 : {
40 :
41 1 : switch(index)
42 : {
43 1 : case MaxSize:
44 : {
45 2 : csmmaxsize = clampvar(false, "csmmaxsize", value, 256, 2048);
46 1 : clearshadowcache();
47 1 : return true;
48 : }
49 0 : case NearPlane:
50 : {
51 0 : csmnearplane = clampvar(false, "csmnearplane", value, 1, 16);
52 0 : return true;
53 : }
54 0 : case FarPlane:
55 : {
56 0 : csmfarplane = clampvar(false, "csmfarplane", value, 64, 16384);
57 0 : return true;
58 : }
59 0 : case Cull:
60 : {
61 0 : csmcull = value;
62 0 : return true;
63 : }
64 0 : case SplitWeight:
65 : {
66 0 : csmsplitweight = clampfvar("csmsplitweight", value, 0.20f, 0.95f);
67 0 : return true;
68 : }
69 0 : case PRadiusTweak:
70 : {
71 0 : csmpradiustweak = clampfvar("csmpradiustweak", value, 1e-3f, 1e3f);
72 0 : return true;
73 : }
74 0 : case DepthRange:
75 : {
76 0 : csmdepthrange = clampfvar("csmdepthrange", value, 0.f, 1e6f);
77 0 : return true;
78 : }
79 0 : case DepthMargin:
80 : {
81 0 : csmdepthmargin = clampfvar("csmdepthmargin", value, 0.f, 1e3f);
82 0 : return true;
83 : }
84 0 : case Bias:
85 : {
86 0 : csmbias = clampfvar("csmbias", value, -1e6f, 1e6f);
87 0 : return true;
88 : }
89 0 : case Bias2:
90 : {
91 0 : csmbias2 = clampfvar("csmbias2", value, -1e16f, 1e6f);
92 0 : return true;
93 : }
94 0 : case Splits:
95 : {
96 0 : csmsplits = clampvar(false, "csmsplits", value, 1, csmmaxsplits);
97 0 : return true;
98 : }
99 0 : case ShadowMap:
100 : {
101 0 : csmshadowmap = value;
102 0 : return true;
103 : }
104 0 : case PolyFactor:
105 : {
106 0 : csmpolyfactor = clampfvar("csmpolyfactor", value, -1e3f, 1e3f);
107 0 : return true;
108 : }
109 0 : case PolyFactor2:
110 : {
111 0 : csmpolyfactor = clampfvar("csmpolyfactor", value, -1e3f, 1e3f);
112 0 : return true;
113 : }
114 0 : case PolyOffset:
115 : {
116 0 : csmpolyfactor = clampfvar("csmpolyfactor", value, -1e4f, 1e4f);
117 0 : return true;
118 : }
119 0 : case PolyOffset2:
120 : {
121 0 : csmpolyfactor = clampfvar("csmpolyfactor", value, -1e4f, 1e4f);
122 0 : return true;
123 : }
124 0 : default:
125 : {
126 0 : return false;
127 : }
128 : }
129 : }
130 :
131 1 : float cascadedshadowmap::getcsmproperty(int index) const
132 : {
133 1 : switch(index)
134 : {
135 1 : case MaxSize:
136 : {
137 1 : return csmmaxsize;
138 : }
139 0 : case NearPlane:
140 : {
141 0 : return csmnearplane;
142 : }
143 0 : case FarPlane:
144 : {
145 0 : return csmfarplane;
146 : }
147 0 : case Cull:
148 : {
149 0 : return csmcull;
150 : }
151 0 : case SplitWeight:
152 : {
153 0 : return csmsplitweight;
154 : }
155 0 : case PRadiusTweak:
156 : {
157 0 : return csmpradiustweak;
158 : }
159 0 : case DepthRange:
160 : {
161 0 : return csmdepthrange;
162 : }
163 0 : case DepthMargin:
164 : {
165 0 : return csmdepthmargin;
166 : }
167 0 : case Bias:
168 : {
169 0 : return csmbias;
170 : }
171 0 : case Bias2:
172 : {
173 0 : return csmbias2;
174 : }
175 0 : case Splits:
176 : {
177 0 : return csmsplits;
178 : }
179 0 : case ShadowMap:
180 : {
181 0 : return csmshadowmap;
182 : }
183 0 : case PolyFactor:
184 : {
185 0 : return csmpolyfactor;
186 : }
187 0 : case PolyFactor2:
188 : {
189 0 : return csmpolyfactor2;
190 : }
191 0 : case PolyOffset:
192 : {
193 0 : return csmpolyoffset;
194 : }
195 0 : case PolyOffset2:
196 : {
197 0 : return csmpolyoffset2;
198 : }
199 0 : default:
200 : {
201 0 : return 0.f;
202 : }
203 : }
204 : }
205 :
206 0 : const matrix4 &cascadedshadowmap::getmodel() const
207 : {
208 0 : return model;
209 : }
210 :
211 0 : const vec &cascadedshadowmap::getlightview() const
212 : {
213 0 : return lightview;
214 : }
215 :
216 0 : int cascadedshadowmap::calcbbsplits(const ivec &bbmin, const ivec &bbmax)
217 : {
218 0 : int mask = (1<<csmsplits)-1;
219 0 : if(!csmcull)
220 : {
221 0 : return mask;
222 : }
223 0 : for(int i = 0; i < csmsplits; ++i)
224 : {
225 0 : const cascadedshadowmap::SplitInfo &split = splits[i];
226 : int k;
227 0 : for(k = 0; k < 4; k++)
228 : {
229 0 : const plane &p = split.cull[k];
230 0 : ivec omin, omax;
231 0 : if(p.x > 0)
232 : {
233 0 : omin.x = bbmin.x;
234 0 : omax.x = bbmax.x;
235 : }
236 : else
237 : {
238 0 : omin.x = bbmax.x;
239 0 : omax.x = bbmin.x;
240 : }
241 0 : if(p.y > 0)
242 : {
243 0 : omin.y = bbmin.y;
244 0 : omax.y = bbmax.y;
245 : }
246 : else
247 : {
248 0 : omin.y = bbmax.y;
249 0 : omax.y = bbmin.y;
250 : }
251 0 : if(p.z > 0)
252 : {
253 0 : omin.z = bbmin.z;
254 0 : omax.z = bbmax.z;
255 : }
256 : else
257 : {
258 0 : omin.z = bbmax.z;
259 0 : omax.z = bbmin.z;
260 : }
261 0 : if(omax.dist(p) < 0)
262 : {
263 0 : mask &= ~(1<<i);
264 0 : goto nextsplit;//skip rest and restart loop
265 : }
266 0 : if(omin.dist(p) < 0)
267 : {
268 0 : goto notinside;
269 : }
270 : }
271 0 : mask &= (2<<i)-1;
272 0 : break;
273 0 : notinside:
274 0 : while(++k < 4)
275 : {
276 0 : const plane &p = split.cull[k];
277 0 : ivec omax(p.x > 0 ? bbmax.x : bbmin.x, p.y > 0 ? bbmax.y : bbmin.y, p.z > 0 ? bbmax.z : bbmin.z);
278 0 : if(omax.dist(p) < 0)
279 : {
280 0 : mask &= ~(1<<i);
281 0 : break;
282 : }
283 : }
284 0 : nextsplit:;
285 : }
286 0 : return mask;
287 : }
288 :
289 0 : int cascadedshadowmap::calcspheresplits(const vec ¢er, float radius) const
290 : {
291 0 : int mask = (1<<csmsplits)-1;
292 0 : if(!csmcull)
293 : {
294 0 : return mask;
295 : }
296 0 : for(int i = 0; i < csmsplits; ++i)
297 : {
298 0 : const cascadedshadowmap::SplitInfo &split = splits[i];
299 : int k;
300 0 : for(k = 0; k < 4; k++)
301 : {
302 0 : const plane &p = split.cull[k];
303 0 : float dist = p.dist(center);
304 0 : if(dist < -radius)
305 : {
306 0 : mask &= ~(1<<i);
307 0 : goto nextsplit; //skip rest and restart loop
308 : }
309 0 : if(dist < radius)
310 : {
311 0 : goto notinside;
312 : }
313 : }
314 0 : mask &= (2<<i)-1;
315 0 : break;
316 0 : notinside:
317 0 : while(++k < 4)
318 : {
319 0 : const plane &p = split.cull[k];
320 0 : if(p.dist(center) < -radius)
321 : {
322 0 : mask &= ~(1<<i);
323 0 : break;
324 : }
325 : }
326 0 : nextsplit:;
327 : }
328 0 : return mask;
329 : }
330 :
331 0 : void cascadedshadowmap::updatesplitdist()
332 : {
333 0 : const float lambda = csmsplitweight,
334 0 : nd = csmnearplane,
335 0 : fd = csmfarplane,
336 0 : ratio = fd/nd;
337 0 : splits[0].nearplane = nd;
338 0 : for(int i = 1; i < csmsplits; ++i)
339 : {
340 0 : const float si = i / static_cast<float>(csmsplits);
341 0 : splits[i].nearplane = lambda*(nd*std::pow(ratio, si)) + (1-lambda)*(nd + (fd - nd)*si);
342 0 : splits[i-1].farplane = splits[i].nearplane * 1.005f;
343 : }
344 0 : splits[csmsplits-1].farplane = fd;
345 0 : }
346 :
347 0 : void cascadedshadowmap::getmodelmatrix()
348 : {
349 0 : model = viewmatrix;
350 0 : model.rotate_around_x(sunlightpitch/RAD);
351 0 : model.rotate_around_z((180-sunlightyaw)/RAD);
352 0 : }
353 :
354 0 : void cascadedshadowmap::getprojmatrix()
355 : {
356 0 : lightview = vec(sunlightdir).neg();
357 :
358 : // compute the split frustums
359 0 : updatesplitdist();
360 :
361 : // find z extent
362 0 : float minz = lightview.project_bb(worldmin, worldmax),
363 0 : maxz = lightview.project_bb(worldmax, worldmin),
364 0 : zmargin = std::max((maxz - minz)*csmdepthmargin, 0.5f*(csmdepthrange - (maxz - minz)));
365 0 : minz -= zmargin;
366 0 : maxz += zmargin;
367 :
368 : // compute each split projection matrix
369 0 : for(int i = 0; i < csmsplits; ++i)
370 : {
371 0 : SplitInfo &split = splits[i];
372 0 : if(split.idx < 0)
373 : {
374 0 : continue;
375 : }
376 0 : const ShadowMapInfo &sm = shadowmaps[split.idx];
377 :
378 0 : vec c;
379 0 : float radius = calcfrustumboundsphere(split.nearplane, split.farplane, camera1->o, camdir(), c);
380 :
381 : // compute the projected bounding box of the sphere
382 0 : vec tc;
383 0 : model.transform(c, tc);
384 0 : int border = smfilter > 2 ? smborder2 : smborder;
385 0 : const float pradius = std::ceil(radius * csmpradiustweak),
386 0 : step = (2*pradius) / (sm.size - 2*border);
387 0 : vec2 offset = vec2(tc).sub(pradius).div(step);
388 0 : offset.x = std::floor(offset.x);
389 0 : offset.y = std::floor(offset.y);
390 0 : split.center = vec(vec2(offset).mul(step).add(pradius), -0.5f*(minz + maxz));
391 0 : split.bounds = vec(pradius, pradius, 0.5f*(maxz - minz));
392 :
393 : // modify mvp with a scale and offset
394 : // now compute the update model view matrix for this split
395 0 : split.scale = vec(1/step, 1/step, -1/(maxz - minz));
396 0 : split.offset = vec(border - offset.x, border - offset.y, -minz/(maxz - minz));
397 :
398 0 : split.proj.identity();
399 0 : split.proj.settranslation(2*split.offset.x/sm.size - 1, 2*split.offset.y/sm.size - 1, 2*split.offset.z - 1);
400 0 : split.proj.setscale(2*split.scale.x/sm.size, 2*split.scale.y/sm.size, 2*split.scale.z);
401 : }
402 0 : }
403 :
404 0 : void cascadedshadowmap::gencullplanes()
405 : {
406 0 : for(int i = 0; i < csmsplits; ++i)
407 : {
408 0 : SplitInfo &split = splits[i];
409 0 : matrix4 mvp;
410 0 : mvp.mul(split.proj, model);
411 0 : vec4<float> px = mvp.rowx(),
412 0 : py = mvp.rowy(),
413 0 : pw = mvp.roww();
414 0 : split.cull[0] = plane(vec4<float>(pw).add(px)).normalize(); // left plane
415 0 : split.cull[1] = plane(vec4<float>(pw).sub(px)).normalize(); // right plane
416 0 : split.cull[2] = plane(vec4<float>(pw).add(py)).normalize(); // bottom plane
417 0 : split.cull[3] = plane(vec4<float>(pw).sub(py)).normalize(); // top plane
418 : }
419 0 : }
420 :
421 0 : void cascadedshadowmap::bindparams()
422 : {
423 0 : GLOBALPARAM(csmmatrix, matrix3(model));
424 :
425 0 : static GlobalShaderParam csmtc("csmtc"),
426 0 : csmoffset("csmoffset");
427 0 : vec4<float> *csmtcv = csmtc.reserve<vec4<float>>();
428 0 : vec *csmoffsetv = csmoffset.reserve<vec>();
429 0 : for(int i = 0; i < csmsplits; ++i)
430 : {
431 0 : cascadedshadowmap::SplitInfo &split = splits[i];
432 0 : if(split.idx < 0)
433 : {
434 0 : continue;
435 : }
436 0 : const ShadowMapInfo &sm = shadowmaps[split.idx];
437 :
438 0 : csmtcv[i] = vec4<float>(vec2(split.center).mul(-split.scale.x), split.scale.x, split.bounds.x*split.scale.x);
439 :
440 0 : const float bias = (smfilter > 2 ? csmbias2 : csmbias) * (-512.0f / sm.size) * (split.farplane - split.nearplane) / (splits[0].farplane - splits[0].nearplane);
441 0 : csmoffsetv[i] = vec(sm.x, sm.y, 0.5f + bias).add2(0.5f*sm.size);
442 : }
443 0 : GLOBALPARAMF(csmz, splits[0].center.z*-splits[0].scale.z, splits[0].scale.z);
444 0 : }
445 :
446 0 : void cascadedshadowmap::setup()
447 : {
448 0 : int size = (csmmaxsize * shadowatlaspacker.dimensions().x) / shadowatlassize;
449 0 : for(int i = 0; i < csmsplits; i++)
450 : {
451 0 : ushort smx = USHRT_MAX,
452 0 : smy = USHRT_MAX;
453 0 : splits[i].idx = -1;
454 0 : if(shadowatlaspacker.insert(smx, smy, size, size))
455 : {
456 0 : addshadowmap(smx, smy, size, splits[i].idx);
457 : }
458 : }
459 0 : getmodelmatrix();
460 0 : getprojmatrix();
461 0 : gencullplanes();
462 0 : }
|