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