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