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