LCOV - code coverage report
Current view: top level - engine/render - csm.cpp (source / functions) Coverage Total Hit
Test: Libprimis Test Coverage Lines: 6.3 % 252 16
Test Date: 2026-06-04 05:03:08 Functions: 23.1 % 13 3

            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 &center, 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 : }
        

Generated by: LCOV version 2.0-1