LCOV - code coverage report
Current view: top level - engine/render - rendersky.cpp (source / functions) Coverage Total Hit
Test: Libprimis Test Coverage Lines: 0.0 % 216 0
Test Date: 2026-06-16 06:16:16 Functions: 0.0 % 16 0

            Line data    Source code
       1              : /**
       2              :  * @file rendersky.cpp
       3              :  * @brief skybox and sky environment rendering
       4              :  *
       5              :  * Libprimis supports standard static cubemap skyboxes as well as cloud layers,
       6              :  * which are both static (though capable of translation/rotation)
       7              :  *
       8              :  * as well as a parameterized sky generation system, referred to the code as the
       9              :  * atmo" functionality
      10              :  *
      11              :  * as expected for distant scenery, like the sky and clouds are, there is no support
      12              :  * for parallax (cloud layers do not move relative to background sky as the player
      13              :  * moves)
      14              :  */
      15              : #include "../libprimis-headers/cube.h"
      16              : #include "../../shared/geomexts.h"
      17              : #include "../../shared/glemu.h"
      18              : #include "../../shared/glexts.h"
      19              : #include "../../shared/stream.h"
      20              : 
      21              : #include <format>
      22              : 
      23              : #include "octarender.h"
      24              : #include "rendergl.h"
      25              : #include "renderlights.h"
      26              : #include "rendersky.h"
      27              : #include "renderva.h"
      28              : #include "shader.h"
      29              : #include "shaderparam.h"
      30              : #include "texture.h"
      31              : 
      32              : #include "interface/console.h"
      33              : #include "interface/control.h"
      34              : 
      35              : #include "world/light.h"
      36              : #include "world/octaedit.h"
      37              : #include "world/octaworld.h"
      38              : #include "world/raycube.h"
      39              : 
      40            0 : VARFR(skyshadow, 0, 0, 1, clearshadowcache());   //toggles rsm features in renderva.cpp
      41              : 
      42              : // internally relevant functionality
      43              : namespace
      44              : {
      45              :     bool explicitsky = false;
      46              : 
      47              :     VARNR(skytexture, useskytexture, 0, 0, 1);       //toggles rendering sky texture instead of nothing on skytex'd geometry
      48              : 
      49              :     std::array<const Texture *, 6> sky = { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr };
      50              : 
      51            0 :     void loadsky(const char *basename, std::array<const Texture *, 6> &texs)
      52              :     {
      53              :         struct cubemapside final
      54              :         {
      55              :             GLenum target;
      56              :             const char *name;
      57              :             bool flipx, flipy, swapxy;
      58              :         };
      59              : 
      60              :         static const std::array<cubemapside, 6> cubemapsides =
      61              :         {{
      62              :             { GL_TEXTURE_CUBE_MAP_NEGATIVE_X, "lf", false, true,  true  },
      63              :             { GL_TEXTURE_CUBE_MAP_POSITIVE_X, "rt", true,  false, true  },
      64              :             { GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, "bk", false, false, false },
      65              :             { GL_TEXTURE_CUBE_MAP_POSITIVE_Y, "ft", true,  true,  false },
      66              :             { GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, "dn", true,  false, true  },
      67              :             { GL_TEXTURE_CUBE_MAP_POSITIVE_Z, "up", true,  false, true  },
      68              :         }};
      69              : 
      70            0 :         const char *wildcard = std::strchr(basename, '*');
      71            0 :         for(size_t i = 0; i < cubemapsides.size(); ++i)
      72              :         {
      73            0 :             const char *side = cubemapsides[i].name;
      74              :             string name;
      75            0 :             copystring(name, makerelpath("media/sky", basename));
      76            0 :             if(wildcard)
      77              :             {
      78            0 :                 char *chop = std::strchr(name, '*');
      79            0 :                 if(chop)
      80              :                 {
      81            0 :                     *chop = '\0';
      82            0 :                     concatstring(name, side);
      83            0 :                     concatstring(name, wildcard+1);
      84              :                 }
      85            0 :                 texs[i] = textureload(name, 3, true, false);
      86              :             }
      87              :             else
      88              :             {
      89            0 :                 std::string ext = std::format("_{}.jpg", side);
      90            0 :                 concatstring(name, ext.c_str());
      91            0 :                 if((texs[i] = textureload(name, 3, true, false))==notexture)
      92              :                 {
      93            0 :                     std::strcpy(name+std::strlen(name)-3, "png");
      94            0 :                     texs[i] = textureload(name, 3, true, false);
      95              :                 }
      96            0 :             }
      97            0 :             if(texs[i]==notexture)
      98              :             {
      99            0 :                 conoutf(Console_Error, "could not load side %s of sky texture %s", side, basename);
     100              :             }
     101              :         }
     102            0 :     }
     103              : 
     104              :     Texture *cloudoverlay = nullptr;
     105              : 
     106            0 :     Texture *loadskyoverlay(std::string_view basename)
     107              :     {
     108            0 :         const char *ext = std::strrchr(basename.data(), '.');
     109              :         string name;
     110            0 :         copystring(name, makerelpath("media/sky", basename.data()));
     111            0 :         Texture *t = notexture;
     112            0 :         if(ext)
     113              :         {
     114            0 :             t = textureload(name, 0, true, false);
     115              :         }
     116              :         else
     117              :         {
     118            0 :             concatstring(name, ".jpg");
     119            0 :             if((t = textureload(name, 0, true, false)) == notexture)
     120              :             {
     121            0 :                 std::strcpy(name+std::strlen(name)-3, "png");
     122            0 :                 t = textureload(name, 0, true, false);
     123              :             }
     124              :         }
     125            0 :         if(t==notexture)
     126              :         {
     127            0 :             conoutf(Console_Error, "could not load sky overlay texture %s", basename.data());
     128              :         }
     129            0 :         return t;
     130              :     }
     131              : 
     132            0 :     SVARFR(skybox, "",
     133              :     {
     134              :         if(skybox[0])
     135              :         {
     136              :             loadsky(skybox, sky);
     137              :         }
     138              :     });                                             //path to skybox
     139            0 :     CVARR(skyboxcolor, 0xFFFFFF);                   //color to multiply skybox texture by
     140              :     FVARR(skyboxoverbright, 1, 2, 16);              //amount by which skybox can exceed 0xFFFFFF
     141              :     FVARR(skyboxoverbrightmin, 0, 1, 16);
     142              :     FVARR(skyboxoverbrightthreshold, 0, 0.7f, 1);
     143              :     FVARR(skyboxspin, -720, 0, 720);                //skybox spin rate in degrees per second
     144              :     VARR (skyboxyaw, 0, 0, 360);                    //skybox rotation offset in degrees
     145              : 
     146              :     //cloud layer variables
     147              :     FVARR(cloudclip, 0, 0.5f, 1);
     148            0 :     SVARFR(cloudlayer, "",
     149              :     {
     150              :         if(cloudlayer[0])
     151              :         {
     152              :             cloudoverlay = loadskyoverlay(cloudlayer);
     153              :         }
     154              :     });
     155              :     FVARR(cloudoffsetx, 0, 0, 1); //offset of cloud texture: 1 is equal to 1 tex-width x
     156              :     FVARR(cloudoffsety, 0, 0, 1); //offset of cloud texture: 1 is equal to 1 tex-width y
     157              :     FVARR(cloudscrollx, -16, 0, 16);
     158              :     FVARR(cloudscrolly, -16, 0, 16);
     159              :     FVARR(cloudscale, 0.001, 1, 64);
     160              :     FVARR(cloudspin, -720, 0, 720);
     161              :     VARR (cloudyaw, 0, 0, 360);
     162              :     FVARR(cloudheight, -1, 0.2f, 1);
     163              :     FVARR(cloudfade, 0, 0.2f, 1);
     164              :     FVARR(cloudalpha, 0, 1, 1);
     165              :     VARR (cloudsubdiv, 4, 16, 64);
     166            0 :     CVARR(cloudcolor, 0xFFFFFF);
     167              : 
     168            0 :     void drawenvboxface(float s0, float t0, int x0, int y0, int z0,
     169              :                         float s1, float t1, int x1, int y1, int z1,
     170              :                         float s2, float t2, int x2, int y2, int z2,
     171              :                         float s3, float t3, int x3, int y3, int z3,
     172              :                         const Texture *tex)
     173              :     {
     174            0 :         glBindTexture(GL_TEXTURE_2D, (tex ? tex : notexture)->id);
     175            0 :         gle::begin(GL_TRIANGLE_STRIP);
     176            0 :         gle::attribf(x3, y3, z3); gle::attribf(s3, t3);
     177            0 :         gle::attribf(x2, y2, z2); gle::attribf(s2, t2);
     178            0 :         gle::attribf(x0, y0, z0); gle::attribf(s0, t0);
     179            0 :         gle::attribf(x1, y1, z1); gle::attribf(s1, t1);
     180            0 :         xtraverts += gle::end();
     181            0 :     }
     182              : 
     183            0 :     void drawenvbox(const std::array<const Texture *, 6> &sky, float z1clip = 0.0f, float z2clip = 1.0f, int faces = 0x3F)
     184              :     {
     185            0 :         if(z1clip >= z2clip)
     186              :         {
     187            0 :             return;
     188              :         }
     189            0 :         float v1 = 1-z1clip,
     190            0 :               v2 = 1-z2clip;
     191            0 :         int w  = farplane/2,
     192            0 :             z1 = static_cast<int>(std::ceil(2*w*(z1clip-0.5f))),
     193            0 :             z2 = static_cast<int>(std::ceil(2*w*(z2clip-0.5f)));
     194              : 
     195            0 :         gle::defvertex();
     196            0 :         gle::deftexcoord0();
     197              : 
     198              :         //daw the six faces of the skybox's cubemap
     199            0 :         if(faces&0x01)
     200              :         {
     201            0 :             drawenvboxface(1.0f, v2,  -w, -w, z2,
     202              :                            0.0f, v2,  -w,  w, z2,
     203              :                            0.0f, v1,  -w,  w, z1,
     204            0 :                            1.0f, v1,  -w, -w, z1, sky[0]);
     205              :         }
     206            0 :         if(faces&0x02)
     207              :         {
     208            0 :             drawenvboxface(0.0f, v1, w, -w, z1,
     209              :                            1.0f, v1, w,  w, z1,
     210              :                            1.0f, v2, w,  w, z2,
     211            0 :                            0.0f, v2, w, -w, z2, sky[1]);
     212              :         }
     213            0 :         if(faces&0x04)
     214              :         {
     215            0 :             drawenvboxface(0.0f, v1, -w, -w, z1,
     216              :                            1.0f, v1,  w, -w, z1,
     217              :                            1.0f, v2,  w, -w, z2,
     218            0 :                            0.0f, v2, -w, -w, z2, sky[2]);
     219              :         }
     220            0 :         if(faces&0x08)
     221              :         {
     222            0 :             drawenvboxface(0.0f, v1,  w,  w, z1,
     223              :                            1.0f, v1, -w,  w, z1,
     224              :                            1.0f, v2, -w,  w, z2,
     225            0 :                            0.0f, v2,  w,  w, z2, sky[3]);
     226              :         }
     227            0 :         if(z1clip <= 0 && faces&0x10)
     228              :         {
     229            0 :             drawenvboxface(1.0f, 1.0f, -w,  w,  -w,
     230              :                            1.0f, 0.0f,  w,  w,  -w,
     231              :                            0.0f, 0.0f,  w, -w,  -w,
     232            0 :                            0.0f, 1.0f, -w, -w,  -w, sky[4]);
     233              :         }
     234            0 :         if(z2clip >= 1 && faces&0x20)
     235              :         {
     236            0 :             drawenvboxface(1.0f, 1.0f,  w,  w, w,
     237              :                            1.0f, 0.0f, -w,  w, w,
     238              :                            0.0f, 0.0f, -w, -w, w,
     239            0 :                            0.0f, 1.0f,  w, -w, w, sky[5]);
     240              :         }
     241              :     }
     242              : 
     243            0 :     void drawenvoverlay(const Texture *overlay = nullptr, float tx = 0, float ty = 0)
     244              :     {
     245            0 :         const int w = farplane/2;
     246            0 :         const float z = w*cloudheight,
     247            0 :                         tsz = 0.5f*(1-cloudfade)/cloudscale,
     248            0 :                         psz = w*(1-cloudfade);
     249            0 :         glBindTexture(GL_TEXTURE_2D, (overlay ? overlay : notexture)->id);
     250            0 :         vec color = cloudcolor.tocolor();
     251            0 :         gle::color(color, cloudalpha);
     252            0 :         gle::defvertex();
     253            0 :         gle::deftexcoord0();
     254            0 :         gle::begin(GL_TRIANGLE_FAN);
     255            0 :         for(int i = 0; i < cloudsubdiv+1; ++i)
     256              :         {
     257            0 :             vec p(1, 1, 0);
     258            0 :             p.rotate_around_z((-2.0f*M_PI*i)/cloudsubdiv);
     259            0 :             gle::attribf(p.x*psz, p.y*psz, z);
     260            0 :             gle::attribf(tx - p.x*tsz, ty + p.y*tsz);
     261              :         }
     262            0 :         xtraverts += gle::end();
     263            0 :         const float tsz2 = 0.5f/cloudscale;
     264            0 :         gle::defvertex();
     265            0 :         gle::deftexcoord0();
     266            0 :         gle::defcolor(4);
     267            0 :         gle::begin(GL_TRIANGLE_STRIP);
     268            0 :         for(int i = 0; i < cloudsubdiv+1; ++i)
     269              :         {
     270            0 :             vec p(1, 1, 0);
     271            0 :             p.rotate_around_z((-2.0f*M_PI*i)/cloudsubdiv);
     272            0 :             gle::attribf(p.x*psz, p.y*psz, z);
     273            0 :             gle::attribf(tx - p.x*tsz, ty + p.y*tsz);
     274            0 :             gle::attrib(color, cloudalpha);
     275            0 :             gle::attribf(p.x*w, p.y*w, z);
     276            0 :             gle::attribf(tx - p.x*tsz2, ty + p.y*tsz2);
     277            0 :             gle::attrib(color, 0.0f);
     278              :         }
     279            0 :         xtraverts += gle::end();
     280            0 :     }
     281              : 
     282              :     /* === "atmo" parameterized, procedurally generated sky === */
     283              :     VARR(atmo, 0, 0, 1);
     284              :     FVARR(atmoplanetsize, 1e-3f, 8, 1e3f);
     285              :     FVARR(atmoheight, 1e-3f, 1, 1e3f);
     286              :     FVARR(atmobright, 0, 4, 16);
     287            0 :     CVAR1R(atmosunlight, 0);
     288              :     FVARR(atmosunlightscale, 0, 1, 16);
     289              :     FVARR(atmosundisksize, 0, 1, 10);
     290              :     FVARR(atmosundiskbright, 0, 1, 16);
     291              :     FVARR(atmohaze, 0, 0.03f, 1);
     292            0 :     CVAR0R(atmohazefade, 0xAEACA9);
     293              :     FVARR(atmohazefadescale, 0, 1, 1);
     294              :     FVARR(atmoclarity, 0, 0.2f, 10);
     295              :     FVARR(atmodensity, 1e-3f, 0.99f, 10);
     296              :     FVARR(atmoalpha, 0, 1, 1);
     297              : 
     298            0 :     void drawatmosphere()
     299              :     {
     300            0 :         SETSHADER(atmosphere);
     301              : 
     302            0 :         matrix4 sunmatrix = cammatrix.inverse();
     303            0 :         sunmatrix.settranslation(0, 0, 0);
     304            0 :         sunmatrix.mul(projmatrix.inverse());
     305            0 :         LOCALPARAM(sunmatrix, sunmatrix);
     306              : 
     307            0 :         LOCALPARAM(sunlight, (!atmosunlight.iszero() ? atmosunlight.tocolor().mul(atmosunlightscale) : sunlight.tocolor().mul(sunlightscale)).mul(atmobright*ldrscale));
     308            0 :         LOCALPARAM(sundir, sunlightdir);
     309              : 
     310            0 :         vec sundiskparams;
     311            0 :         sundiskparams.y = -(1 - 0.0075f * atmosundisksize);
     312            0 :         sundiskparams.x = 1/(1 + sundiskparams.y);
     313            0 :         sundiskparams.y *= sundiskparams.x;
     314            0 :         sundiskparams.z = atmosundiskbright;
     315            0 :         LOCALPARAM(sundiskparams, sundiskparams);
     316              : 
     317            0 :         const float earthradius     = 6.371e6f, //radius of earth in meters
     318            0 :                     earthatmoheight = 100e3f; //atmospheric height (100km)
     319            0 :         const float planetradius = earthradius*atmoplanetsize,
     320            0 :                     atmoradius   = planetradius + earthatmoheight*atmoheight;
     321            0 :         LOCALPARAMF(atmoradius, planetradius, atmoradius*atmoradius, atmoradius*atmoradius - planetradius*planetradius);
     322              : 
     323            0 :         const float gm = (1 - atmohaze)*0.2f + 0.75f;
     324            0 :         LOCALPARAMF(gm, gm);
     325              : 
     326            0 :         const vec lambda(680e-9f, 550e-9f, 450e-9f);
     327            0 :         vec betar = vec(lambda).square().square().recip().mul(1.86e-31f / atmodensity),
     328            0 :             betam = vec(lambda).recip().mul(2*M_PI).square().mul(atmohazefade.tocolor().mul(atmohazefadescale)).mul(1.36e-19f * std::max(atmohaze, 1e-3f)),
     329            0 :             betarm = vec(betar).div(1+atmoclarity).add(betam);
     330            0 :         betar.div(betarm).mul(3/(16*M_PI));
     331            0 :         betam.div(betarm).mul((1-gm)*(1-gm)/(4*M_PI));
     332            0 :         LOCALPARAM(betar, betar);
     333            0 :         LOCALPARAM(betam, betam);
     334            0 :         LOCALPARAM(betarm, betarm.div(M_LN2));
     335              : 
     336            0 :         LOCALPARAMF(atmoalpha, atmoalpha);
     337              : 
     338            0 :         gle::defvertex();
     339            0 :         gle::begin(GL_TRIANGLE_STRIP);
     340            0 :         gle::attribf(-1, 1, 1);
     341            0 :         gle::attribf(1, 1, 1);
     342            0 :         gle::attribf(-1, -1, 1);
     343            0 :         gle::attribf(1, -1, 1);
     344            0 :         xtraverts += gle::end();
     345            0 :     }
     346              : 
     347              :     /* === general sky rendering === */
     348              :     VAR(showsky, 0, 1, 1);
     349              :     VAR(clampsky, 0, 1, 1);
     350              : }
     351              : 
     352              : // externally relevant functionality
     353              : 
     354            0 : void setexplicitsky(bool val)
     355              : {
     356            0 :     explicitsky = val;
     357            0 : }
     358              : 
     359            0 : bool limitsky()
     360              : {
     361            0 :     return explicitsky && (useskytexture || editmode);
     362              : }
     363              : 
     364            0 : void drawskybox(bool clear)
     365              : {
     366            0 :     bool limited = false;
     367            0 :     if(limitsky())
     368              :     {
     369            0 :         for(const vtxarray *va = visibleva; va; va = va->next)
     370              :         {
     371            0 :             if(va->sky && va->occluded < Occlude_BB &&
     372            0 :                ((va->skymax.x >= 0 && view.isvisiblebb(va->skymin, ivec(va->skymax).sub(va->skymin)) != ViewFrustumCull_NotVisible) ||
     373            0 :                 !insideworld(camera1->o)))
     374              :             {
     375            0 :                 limited = true;
     376            0 :                 break;
     377              :             }
     378              :         }
     379              :     }
     380            0 :     if(limited)
     381              :     {
     382            0 :         glDisable(GL_DEPTH_TEST);
     383              :     }
     384              :     else
     385              :     {
     386            0 :         glDepthFunc(GL_LEQUAL);
     387            0 :         glDepthMask(GL_FALSE);
     388              :     }
     389            0 :     if(clampsky)
     390              :     {
     391            0 :         glDepthRange(1, 1); //set gl depth range min and max to 1 (all far away)
     392              :     }
     393            0 :     if(clear || (!skybox[0] && (!atmo || atmoalpha < 1)))
     394              :     {
     395            0 :         vec skyboxcol = skyboxcolor.tocolor().mul(ldrscale); //local skyboxcol was skyboxcolor before skyboxcolour -> skyboxcolor uniformity change
     396            0 :         glClearColor(skyboxcol.x, skyboxcol.y, skyboxcol.z, 0);
     397            0 :         glClear(GL_COLOR_BUFFER_BIT);
     398              :     }
     399            0 :     if(skybox[0])
     400              :     {
     401            0 :         if(ldrscale < 1 && (skyboxoverbrightmin != 1 || (skyboxoverbright > 1 && skyboxoverbrightthreshold < 1)))
     402              :         {
     403            0 :             SETSHADER(skyboxoverbright);
     404            0 :             LOCALPARAMF(overbrightparams, skyboxoverbrightmin, std::max(skyboxoverbright, skyboxoverbrightmin), skyboxoverbrightthreshold);
     405            0 :         }
     406              :         else
     407              :         {
     408            0 :             SETSHADER(skybox);
     409              :         }
     410            0 :         gle::color(skyboxcolor);
     411              : 
     412            0 :         matrix4 skymatrix = cammatrix,
     413            0 :                 skyprojmatrix;
     414            0 :         skymatrix.settranslation(0, 0, 0);
     415            0 :         skymatrix.rotate_around_z((skyboxspin*lastmillis/1000.0f+skyboxyaw)/RAD);
     416            0 :         skyprojmatrix.mul(projmatrix, skymatrix);
     417            0 :         LOCALPARAM(skymatrix, skyprojmatrix);
     418              : 
     419            0 :         drawenvbox(sky);
     420              :     }
     421            0 :     if(atmo && (!skybox[0] || atmoalpha < 1))
     422              :     {
     423            0 :         if(atmoalpha < 1)
     424              :         {
     425            0 :             glEnable(GL_BLEND);
     426            0 :             glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
     427              :         }
     428              : 
     429            0 :         drawatmosphere();
     430              : 
     431            0 :         if(atmoalpha < 1)
     432              :         {
     433            0 :             glDisable(GL_BLEND);
     434              :         }
     435              :     }
     436            0 :     if(cloudlayer[0] && cloudheight)
     437              :     {
     438            0 :         SETSHADER(skybox);
     439              : 
     440            0 :         glDisable(GL_CULL_FACE);
     441              : 
     442            0 :         glEnable(GL_BLEND);
     443            0 :         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
     444              : 
     445            0 :         matrix4 skymatrix = cammatrix,
     446            0 :                 skyprojmatrix;
     447            0 :         skymatrix.settranslation(0, 0, 0);
     448            0 :         skymatrix.rotate_around_z((cloudspin*lastmillis/1000.0f+cloudyaw)/RAD);
     449            0 :         skyprojmatrix.mul(projmatrix, skymatrix);
     450            0 :         LOCALPARAM(skymatrix, skyprojmatrix);
     451              : 
     452            0 :         drawenvoverlay(cloudoverlay, cloudoffsetx + cloudscrollx * lastmillis/1000.0f, cloudoffsety + cloudscrolly * lastmillis/1000.0f);
     453              : 
     454            0 :         glDisable(GL_BLEND);
     455              : 
     456            0 :         glEnable(GL_CULL_FACE);
     457              :     }
     458            0 :     if(clampsky)
     459              :     {
     460            0 :         glDepthRange(0, 1); //return depth range to normal
     461              :     }
     462            0 :     if(limited)
     463              :     {
     464            0 :         glEnable(GL_DEPTH_TEST);
     465              :     }
     466              :     else
     467              :     {
     468            0 :         glDepthMask(GL_TRUE);
     469            0 :         glDepthFunc(GL_LESS);
     470              :     }
     471            0 : }
        

Generated by: LCOV version 2.0-1