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