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 : }
|