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 :
52 : struct cubemapside
53 : {
54 : GLenum target;
55 : const char *name;
56 : bool flipx, flipy, swapxy;
57 : };
58 : static const cubemapside cubemapsides[6] =
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 : float planetradius = earthradius*atmoplanetsize,
318 0 : atmoradius = planetradius + earthatmoheight*atmoheight;
319 0 : LOCALPARAMF(atmoradius, planetradius, atmoradius*atmoradius, atmoradius*atmoradius - planetradius*planetradius);
320 :
321 0 : float gm = (1 - atmohaze)*0.2f + 0.75f;
322 0 : LOCALPARAMF(gm, gm);
323 :
324 0 : vec lambda(680e-9f, 550e-9f, 450e-9f),
325 0 : 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 : bool limitsky()
353 : {
354 0 : return explicitsky && (useskytexture || editmode);
355 : }
356 :
357 0 : void drawskybox(bool clear)
358 : {
359 0 : bool limited = false;
360 0 : if(limitsky())
361 : {
362 0 : for(vtxarray *va = visibleva; va; va = va->next)
363 : {
364 0 : if(va->sky && va->occluded < Occlude_BB &&
365 0 : ((va->skymax.x >= 0 && view.isvisiblebb(va->skymin, ivec(va->skymax).sub(va->skymin)) != ViewFrustumCull_NotVisible) ||
366 0 : !insideworld(camera1->o)))
367 : {
368 0 : limited = true;
369 0 : break;
370 : }
371 : }
372 : }
373 0 : if(limited)
374 : {
375 0 : glDisable(GL_DEPTH_TEST);
376 : }
377 : else
378 : {
379 0 : glDepthFunc(GL_LEQUAL);
380 0 : glDepthMask(GL_FALSE);
381 : }
382 0 : if(clampsky)
383 : {
384 0 : glDepthRange(1, 1); //set gl depth range min and max to 1 (all far away)
385 : }
386 0 : if(clear || (!skybox[0] && (!atmo || atmoalpha < 1)))
387 : {
388 0 : vec skyboxcol = skyboxcolor.tocolor().mul(ldrscale); //local skyboxcol was skyboxcolor before skyboxcolour -> skyboxcolor uniformity change
389 0 : glClearColor(skyboxcol.x, skyboxcol.y, skyboxcol.z, 0);
390 0 : glClear(GL_COLOR_BUFFER_BIT);
391 : }
392 0 : if(skybox[0])
393 : {
394 0 : if(ldrscale < 1 && (skyboxoverbrightmin != 1 || (skyboxoverbright > 1 && skyboxoverbrightthreshold < 1)))
395 : {
396 0 : SETSHADER(skyboxoverbright);
397 0 : LOCALPARAMF(overbrightparams, skyboxoverbrightmin, std::max(skyboxoverbright, skyboxoverbrightmin), skyboxoverbrightthreshold);
398 0 : }
399 : else
400 : {
401 0 : SETSHADER(skybox);
402 : }
403 0 : gle::color(skyboxcolor);
404 :
405 0 : matrix4 skymatrix = cammatrix,
406 0 : skyprojmatrix;
407 0 : skymatrix.settranslation(0, 0, 0);
408 0 : skymatrix.rotate_around_z((skyboxspin*lastmillis/1000.0f+skyboxyaw)/RAD);
409 0 : skyprojmatrix.mul(projmatrix, skymatrix);
410 0 : LOCALPARAM(skymatrix, skyprojmatrix);
411 :
412 0 : drawenvbox(sky);
413 : }
414 0 : if(atmo && (!skybox[0] || atmoalpha < 1))
415 : {
416 0 : if(atmoalpha < 1)
417 : {
418 0 : glEnable(GL_BLEND);
419 0 : glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
420 : }
421 :
422 0 : drawatmosphere();
423 :
424 0 : if(atmoalpha < 1)
425 : {
426 0 : glDisable(GL_BLEND);
427 : }
428 : }
429 0 : if(cloudlayer[0] && cloudheight)
430 : {
431 0 : SETSHADER(skybox);
432 :
433 0 : glDisable(GL_CULL_FACE);
434 :
435 0 : glEnable(GL_BLEND);
436 0 : glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
437 :
438 0 : matrix4 skymatrix = cammatrix,
439 0 : skyprojmatrix;
440 0 : skymatrix.settranslation(0, 0, 0);
441 0 : skymatrix.rotate_around_z((cloudspin*lastmillis/1000.0f+cloudyaw)/RAD);
442 0 : skyprojmatrix.mul(projmatrix, skymatrix);
443 0 : LOCALPARAM(skymatrix, skyprojmatrix);
444 :
445 0 : drawenvoverlay(cloudoverlay, cloudoffsetx + cloudscrollx * lastmillis/1000.0f, cloudoffsety + cloudscrolly * lastmillis/1000.0f);
446 :
447 0 : glDisable(GL_BLEND);
448 :
449 0 : glEnable(GL_CULL_FACE);
450 : }
451 0 : if(clampsky)
452 : {
453 0 : glDepthRange(0, 1); //return depth range to normal
454 : }
455 0 : if(limited)
456 : {
457 0 : glEnable(GL_DEPTH_TEST);
458 : }
459 : else
460 : {
461 0 : glDepthMask(GL_TRUE);
462 0 : glDepthFunc(GL_LESS);
463 : }
464 0 : }
|