1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2
3 #include <cfloat>
4
5 #include "ShadowHandler.h"
6 #include "Game/Camera.h"
7 #include "Game/GameVersion.h"
8 #include "Map/BaseGroundDrawer.h"
9 #include "Map/MapInfo.h"
10 #include "Map/ReadMap.h"
11 #include "Rendering/GlobalRendering.h"
12 #include "Rendering/FeatureDrawer.h"
13 #include "Rendering/ProjectileDrawer.h"
14 #include "Rendering/UnitDrawer.h"
15 #include "Rendering/Env/GrassDrawer.h"
16 #include "Rendering/Env/ISky.h"
17 #include "Rendering/Env/ITreeDrawer.h"
18 #include "Rendering/GL/FBO.h"
19 #include "Rendering/GL/myGL.h"
20 #include "Rendering/GL/VertexArray.h"
21 #include "Rendering/Models/ModelDrawer.h"
22 #include "Rendering/Shaders/ShaderHandler.h"
23 #include "System/Config/ConfigHandler.h"
24 #include "System/EventHandler.h"
25 #include "System/Matrix44f.h"
26 #include "System/myMath.h"
27 #include "System/Log/ILog.h"
28
29 #define SHADOWMATRIX_NONLINEAR 0
30
31 CONFIG(int, Shadows).defaultValue(2).minimumValue(-1).safemodeValue(-1).description("Sets whether shadows are rendered.\n-1:=forceoff, 0:=off, 1:=full, 2:=fast (skip terrain)"); //FIXME document bitmask
32 CONFIG(int, ShadowMapSize).defaultValue(CShadowHandler::DEF_SHADOWMAP_SIZE).minimumValue(32).description("Sets the resolution of shadows. Higher numbers increase quality at the cost of performance.");
33 CONFIG(int, ShadowProjectionMode).defaultValue(CShadowHandler::SHADOWPROMODE_CAM_CENTER);
34
35 CShadowHandler* shadowHandler = NULL;
36
37 bool CShadowHandler::shadowsSupported = false;
38 bool CShadowHandler::firstInit = true;
39
40
Reload(const char * argv)41 void CShadowHandler::Reload(const char* argv)
42 {
43 int nextShadowConfig = (shadowConfig + 1) & 0xF;
44 int nextShadowMapSize = shadowMapSize;
45 int nextShadowProMode = shadowProMode;
46
47 if (argv != NULL) {
48 (void) sscanf(argv, "%i %i %i", &nextShadowConfig, &nextShadowMapSize, &nextShadowProMode);
49 }
50
51 configHandler->Set("Shadows", nextShadowConfig & 0xF);
52 configHandler->Set("ShadowMapSize", Clamp(nextShadowMapSize, int(MIN_SHADOWMAP_SIZE), int(MAX_SHADOWMAP_SIZE)));
53 configHandler->Set("ShadowProjectionMode", Clamp(nextShadowProMode, int(SHADOWPROMODE_MAP_CENTER), int(SHADOWPROMODE_MIX_CAMMAP)));
54
55 Kill();
56 Init();
57 }
58
Init()59 void CShadowHandler::Init()
60 {
61 const bool tmpFirstInit = firstInit;
62 firstInit = false;
63
64 shadowConfig = configHandler->GetInt("Shadows");
65 shadowMapSize = configHandler->GetInt("ShadowMapSize");
66 shadowProMode = configHandler->GetInt("ShadowProjectionMode");
67 shadowGenBits = SHADOWGEN_BIT_NONE;
68
69 shadowsLoaded = false;
70 inShadowPass = false;
71
72 shadowTexture = 0;
73 dummyColorTexture = 0;
74
75 if (!tmpFirstInit && !shadowsSupported) {
76 return;
77 }
78
79 // possible values for the "Shadows" config-parameter:
80 // < 0: disable and don't try to initialize
81 // 0: disable, but still check if the hardware is able to run them
82 // > 0: enabled (by default for all shadow-casting geometry if equal to 1)
83 if (shadowConfig < 0) {
84 LOG("[%s] shadow rendering is disabled (config-value %d)", __FUNCTION__, shadowConfig);
85 return;
86 }
87
88 if (shadowConfig > 0)
89 shadowGenBits = SHADOWGEN_BIT_MODEL | SHADOWGEN_BIT_MAP | SHADOWGEN_BIT_PROJ | SHADOWGEN_BIT_TREE;
90
91 if (shadowConfig > 1) {
92 shadowGenBits &= (~shadowConfig);
93 }
94
95 // no warnings when running headless
96 if (SpringVersion::IsHeadless())
97 return;
98
99 if (!globalRendering->haveARB && !globalRendering->haveGLSL) {
100 LOG_L(L_WARNING, "[%s] GPU does not support either ARB or GLSL shaders for shadow rendering", __FUNCTION__);
101 return;
102 }
103
104 if (!globalRendering->haveGLSL) {
105 if (!GLEW_ARB_shadow || !GLEW_ARB_depth_texture || !GLEW_ARB_texture_env_combine) {
106 LOG_L(L_WARNING, "[%s] required OpenGL ARB-extensions missing for shadow rendering", __FUNCTION__);
107 // NOTE: these should only be relevant for FFP shadows
108 // return;
109 }
110 if (!GLEW_ARB_shadow_ambient) {
111 // can't use arbitrary texvals in case the depth comparison op fails (only 0)
112 LOG_L(L_WARNING, "[%s] \"ARB_shadow_ambient\" extension missing (will probably make shadows darker than they should be)", __FUNCTION__);
113 }
114 }
115
116
117 if (!InitDepthTarget()) {
118 // free any resources allocated by InitDepthTarget()
119 FreeTextures();
120
121 LOG_L(L_ERROR, "[%s] failed to initialize depth-texture FBO", __FUNCTION__);
122 return;
123 }
124
125 if (tmpFirstInit) {
126 shadowsSupported = true;
127 }
128
129 if (shadowConfig == 0) {
130 // free any resources allocated by InitDepthTarget()
131 FreeTextures();
132
133 // shadowsLoaded is still false
134 return;
135 }
136
137 LoadShadowGenShaderProgs();
138 }
139
Kill()140 void CShadowHandler::Kill()
141 {
142 FreeTextures();
143 shaderHandler->ReleaseProgramObjects("[ShadowHandler]");
144 shadowGenProgs.clear();
145 }
146
FreeTextures()147 void CShadowHandler::FreeTextures() {
148 if (fb.IsValid()) {
149 fb.Bind();
150 fb.DetachAll();
151 fb.Unbind();
152 }
153
154 glDeleteTextures(1, &shadowTexture ); shadowTexture = 0;
155 glDeleteTextures(1, &dummyColorTexture); dummyColorTexture = 0;
156 }
157
158
159
LoadShadowGenShaderProgs()160 void CShadowHandler::LoadShadowGenShaderProgs()
161 {
162 #define sh shaderHandler
163 shadowGenProgs.resize(SHADOWGEN_PROGRAM_LAST);
164
165 static const std::string shadowGenProgNames[SHADOWGEN_PROGRAM_LAST] = {
166 "ARB/unit_genshadow.vp",
167 "ARB/groundshadow.vp",
168 "ARB/treeShadow.vp",
169 "ARB/treeFarShadow.vp",
170 "ARB/projectileshadow.vp",
171 };
172 static const std::string shadowGenProgHandles[SHADOWGEN_PROGRAM_LAST] = {
173 "ShadowGenShaderProgModel",
174 "ShadowGenshaderProgMap",
175 "ShadowGenshaderProgTreeNear",
176 "ShadowGenshaderProgTreeDist",
177 "ShadowGenshaderProgProjectile",
178 };
179 static const std::string shadowGenProgDefines[SHADOWGEN_PROGRAM_LAST] = {
180 "#define SHADOWGEN_PROGRAM_MODEL\n",
181 "#define SHADOWGEN_PROGRAM_MAP\n",
182 "#define SHADOWGEN_PROGRAM_TREE_NEAR\n",
183 "#define SHADOWGEN_PROGRAM_TREE_DIST\n",
184 "#define SHADOWGEN_PROGRAM_PROJECTILE\n",
185 };
186
187 static const std::string extraDef =
188 #if (SHADOWMATRIX_NONLINEAR == 1)
189 "#define SHADOWMATRIX_NONLINEAR 0\n";
190 #else
191 "#define SHADOWMATRIX_NONLINEAR 1\n";
192 #endif
193
194 if (globalRendering->haveGLSL) {
195 for (int i = 0; i < SHADOWGEN_PROGRAM_LAST; i++) {
196 Shader::IProgramObject* po = sh->CreateProgramObject("[ShadowHandler]", shadowGenProgHandles[i] + "GLSL", false);
197 Shader::IShaderObject* so = sh->CreateShaderObject("GLSL/ShadowGenVertProg.glsl", shadowGenProgDefines[i] + extraDef, GL_VERTEX_SHADER);
198
199 po->AttachShaderObject(so);
200 po->Link();
201 po->SetUniformLocation("shadowParams");
202 po->SetUniformLocation("cameraDirX"); // used by SHADOWGEN_PROGRAM_TREE_NEAR
203 po->SetUniformLocation("cameraDirY"); // used by SHADOWGEN_PROGRAM_TREE_NEAR
204 po->SetUniformLocation("treeOffset"); // used by SHADOWGEN_PROGRAM_TREE_NEAR
205 po->Validate();
206
207 shadowGenProgs[i] = po;
208 }
209 } else {
210 for (int i = 0; i < SHADOWGEN_PROGRAM_LAST; i++) {
211 Shader::IProgramObject* po = sh->CreateProgramObject("[ShadowHandler]", shadowGenProgHandles[i] + "ARB", true);
212 Shader::IShaderObject* so = sh->CreateShaderObject(shadowGenProgNames[i], "", GL_VERTEX_PROGRAM_ARB);
213
214 po->AttachShaderObject(so);
215 po->Link();
216
217 shadowGenProgs[i] = po;
218 }
219 }
220
221 shadowsLoaded = true;
222 #undef sh
223 }
224
225
226
InitDepthTarget()227 bool CShadowHandler::InitDepthTarget()
228 {
229 // this can be enabled for debugging
230 // it turns the shadow render buffer in a buffer with color
231 bool useColorTexture = false;
232 if (!fb.IsValid()) {
233 LOG_L(L_ERROR, "[%s] framebuffer not valid", __FUNCTION__);
234 return false;
235 }
236
237 fb.Bind();
238
239 glGenTextures(1, &shadowTexture);
240 glBindTexture(GL_TEXTURE_2D, shadowTexture);
241 const float one[4] = {1.0f, 1.0f, 1.0f, 1.0f};
242 glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, one);
243 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
244 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
245 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // shadowtex linear sampling is for-free on NVidias
246 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
247 if (useColorTexture) {
248 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, shadowMapSize, shadowMapSize, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
249 fb.AttachTexture(shadowTexture);
250 } else {
251 const GLint texFormat = GL_DEPTH_COMPONENT32;
252 glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_LUMINANCE);
253 glTexImage2D(GL_TEXTURE_2D, 0, texFormat, shadowMapSize, shadowMapSize, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
254 fb.AttachTexture(shadowTexture, GL_TEXTURE_2D, GL_DEPTH_ATTACHMENT_EXT);
255 }
256
257 // test the FBO
258 glDrawBuffer(useColorTexture ? GL_COLOR_ATTACHMENT0_EXT : GL_NONE);
259 glDrawBuffer(useColorTexture ? GL_COLOR_ATTACHMENT0_EXT : GL_NONE);
260 bool status = fb.CheckStatus("SHADOW");
261 if (!status && !useColorTexture) {
262 status = WorkaroundUnsupportedFboRenderTargets();
263 }
264 fb.Unbind();
265 return status;
266 }
267
268
WorkaroundUnsupportedFboRenderTargets()269 bool CShadowHandler::WorkaroundUnsupportedFboRenderTargets()
270 {
271 bool status = false;
272
273 // some drivers/GPUs fail to render to GL_CLAMP_TO_BORDER (and GL_LINEAR may cause a drop in performance for them, too)
274 {
275 fb.Detach(GL_DEPTH_ATTACHMENT_EXT);
276 glDeleteTextures(1, &shadowTexture);
277
278 glGenTextures(1, &shadowTexture);
279 glBindTexture(GL_TEXTURE_2D, shadowTexture);
280 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
281 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
282 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
283 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
284 const GLint texFormat = globalRendering->support24bitDepthBuffers ? GL_DEPTH_COMPONENT24 : GL_DEPTH_COMPONENT16;
285 glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_LUMINANCE);
286 glTexImage2D(GL_TEXTURE_2D, 0, texFormat, shadowMapSize, shadowMapSize, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
287 fb.AttachTexture(shadowTexture, GL_TEXTURE_2D, GL_DEPTH_ATTACHMENT_EXT);
288 status = fb.CheckStatus("SHADOW-GL_CLAMP_TO_EDGE");
289 if (status)
290 return true;
291 }
292
293
294 // ATI sometimes fails without an attached color texture, so check a few formats (not all supported texture formats are renderable)
295 {
296 glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
297 glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
298
299 // 1st: try the smallest unsupported format (4bit per pixel)
300 glGenTextures(1, &dummyColorTexture);
301 glBindTexture(GL_TEXTURE_2D, dummyColorTexture);
302 glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA4, shadowMapSize, shadowMapSize, 0, GL_ALPHA, GL_UNSIGNED_BYTE, NULL);
303 fb.AttachTexture(dummyColorTexture);
304 status = fb.CheckStatus("SHADOW-GL_ALPHA4");
305 if (status)
306 return true;
307
308 // failed revert changes of 1st attempt
309 fb.Detach(GL_COLOR_ATTACHMENT0_EXT);
310 glDeleteTextures(1, &dummyColorTexture);
311
312 // 2nd: try smallest standard format that must be renderable for OGL3
313 fb.CreateRenderBuffer(GL_COLOR_ATTACHMENT0_EXT, GL_RED, shadowMapSize, shadowMapSize);
314 status = fb.CheckStatus("SHADOW-GL_RED");
315 if (status)
316 return true;
317 }
318
319 return status;
320 }
321
322
DrawShadowPasses()323 void CShadowHandler::DrawShadowPasses()
324 {
325 inShadowPass = true;
326
327 glPushAttrib(GL_POLYGON_BIT | GL_ENABLE_BIT);
328 glEnable(GL_CULL_FACE);
329
330 glCullFace(GL_BACK);
331 eventHandler.DrawWorldShadow();
332
333 if ((shadowGenBits & SHADOWGEN_BIT_TREE) != 0) {
334 treeDrawer->DrawShadowPass();
335 grassDrawer->DrawShadow();
336 }
337
338 if ((shadowGenBits & SHADOWGEN_BIT_PROJ) != 0)
339 projectileDrawer->DrawShadowPass();
340
341 if ((shadowGenBits & SHADOWGEN_BIT_MODEL) != 0) {
342 unitDrawer->DrawShadowPass();
343 modelDrawer->Draw();
344 featureDrawer->DrawShadowPass();
345 }
346
347 glCullFace(GL_FRONT);
348 // cull front-faces during the terrain shadow pass: sun direction
349 // can be set so oblique that geometry back-faces are visible (eg.
350 // from hills near map edges) from its POV
351 // (could just disable culling of terrain faces, but we also want
352 // to prevent overdraw in such low-angle passes)
353 if ((shadowGenBits & SHADOWGEN_BIT_MAP) != 0)
354 readMap->GetGroundDrawer()->DrawShadowPass();
355 glPopAttrib();
356
357 inShadowPass = false;
358 }
359
SetShadowMapSizeFactors()360 void CShadowHandler::SetShadowMapSizeFactors()
361 {
362 #if (SHADOWMATRIX_NONLINEAR == 1)
363 // note: depends on CalcMinMaxView(), which is no longer called
364 const float shadowMapX = math::sqrt( math::fabs(shadowProjMinMax.y) ); // math::sqrt( |x2| )
365 const float shadowMapY = math::sqrt( math::fabs(shadowProjMinMax.w) ); // math::sqrt( |y2| )
366 const float shadowMapW = shadowMapX + math::sqrt( math::fabs(shadowProjMinMax.x) ); // math::sqrt( |x2| ) + math::sqrt( |x1| )
367 const float shadowMapH = shadowMapY + math::sqrt( math::fabs(shadowProjMinMax.z) ); // math::sqrt( |y2| ) + math::sqrt( |y1| )
368
369 shadowTexProjCenter.x = 1.0f - (shadowMapX / shadowMapW);
370 shadowTexProjCenter.y = 1.0f - (shadowMapY / shadowMapH);
371
372 if (shadowMapSize >= 2048) {
373 shadowTexProjCenter.z = 0.01f;
374 shadowTexProjCenter.w = -0.1f;
375 } else {
376 shadowTexProjCenter.z = 0.0025f;
377 shadowTexProjCenter.w = -0.05f;
378 }
379 #else
380 shadowTexProjCenter.x = 0.5f;
381 shadowTexProjCenter.y = 0.5f;
382 shadowTexProjCenter.z = FLT_MAX;
383 shadowTexProjCenter.w = 1.0f;
384 #endif
385 }
386
CreateShadows()387 void CShadowHandler::CreateShadows()
388 {
389 fb.Bind();
390
391 glDisable(GL_BLEND);
392 glDisable(GL_LIGHTING);
393 glDisable(GL_ALPHA_TEST);
394 glDisable(GL_TEXTURE_2D);
395
396 glShadeModel(GL_FLAT);
397 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
398 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
399 glDepthMask(GL_TRUE);
400 glEnable(GL_DEPTH_TEST);
401
402 glViewport(0, 0, shadowMapSize, shadowMapSize);
403
404 // glClearColor(0, 0, 0, 0);
405 // glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
406 glClear(GL_DEPTH_BUFFER_BIT);
407
408 glMatrixMode(GL_PROJECTION);
409 glLoadIdentity();
410 glOrtho(0, 1, 0, 1, 0, -1);
411
412 glMatrixMode(GL_MODELVIEW);
413 glLoadIdentity();
414
415
416 const ISkyLight* L = sky->GetLight();
417
418 // sun direction is in world-space, invert it
419 sunDirZ = -float3(L->GetLightDir());
420 sunDirX = (sunDirZ.cross(UpVector)).ANormalize();
421 sunDirY = (sunDirX.cross(sunDirZ)).ANormalize();
422
423 SetShadowMapSizeFactors();
424
425 // NOTE:
426 // the xy-scaling factors from CalcMinMaxView do not change linearly
427 // or smoothly with camera movements, creating visible artefacts (eg.
428 // large jumps in shadow resolution)
429 //
430 // therefore, EITHER use "fixed" scaling values such that the entire
431 // map barely fits into the sun's frustum (by pretending it is embedded
432 // in a sphere and taking its diameter), OR variable scaling such that
433 // everything that can be seen by the camera maximally fills the sun's
434 // frustum (choice of projection-style is left to the user and can be
435 // changed at run-time)
436 //
437 // the first option means larger maps will have more blurred/aliased
438 // shadows if the depth buffer is kept at the same size, but no (map)
439 // geometry is ever omitted
440 //
441 // the second option means shadows have higher average resolution, but
442 // become less sharp as the viewing volume increases (through eg.camera
443 // rotations) and geometry can be omitted in some cases
444 //
445 // NOTE:
446 // when DynamicSun is enabled, the orbit is always circular in the xz
447 // plane, instead of elliptical when the map has an aspect-ratio != 1
448 //
449 const float xyScale = GetShadowProjectionRadius(camera, centerPos, -sunDirZ);
450 const float xScale = xyScale;
451 const float yScale = xyScale;
452 const float zScale = globalRendering->viewRange;
453
454 shadowMatrix[ 0] = sunDirX.x / xScale;
455 shadowMatrix[ 1] = sunDirY.x / yScale;
456 shadowMatrix[ 2] = sunDirZ.x / zScale;
457
458 shadowMatrix[ 4] = sunDirX.y / xScale;
459 shadowMatrix[ 5] = sunDirY.y / yScale;
460 shadowMatrix[ 6] = sunDirZ.y / zScale;
461
462 shadowMatrix[ 8] = sunDirX.z / xScale;
463 shadowMatrix[ 9] = sunDirY.z / yScale;
464 shadowMatrix[10] = sunDirZ.z / zScale;
465
466 // rotate the target position into sun-space for the translation
467 shadowMatrix[12] = (-sunDirX.dot(centerPos) / xScale);
468 shadowMatrix[13] = (-sunDirY.dot(centerPos) / yScale);
469 shadowMatrix[14] = (-sunDirZ.dot(centerPos) / zScale) + 0.5f;
470
471 glLoadMatrixf(shadowMatrix.m);
472
473 // set the shadow-parameter registers
474 // NOTE: so long as any part of Spring rendering still uses
475 // ARB programs at run-time, these lines can not be removed
476 // (all ARB programs share the same environment)
477 glProgramEnvParameter4fARB(GL_VERTEX_PROGRAM_ARB, 16, shadowTexProjCenter.x, shadowTexProjCenter.y, 0.0f, 0.0f);
478 glProgramEnvParameter4fARB(GL_VERTEX_PROGRAM_ARB, 17, shadowTexProjCenter.z, shadowTexProjCenter.z, 0.0f, 0.0f);
479 glProgramEnvParameter4fARB(GL_VERTEX_PROGRAM_ARB, 18, shadowTexProjCenter.w, shadowTexProjCenter.w, 0.0f, 0.0f);
480
481 if (globalRendering->haveGLSL) {
482 for (int i = 0; i < SHADOWGEN_PROGRAM_LAST; i++) {
483 shadowGenProgs[i]->Enable();
484 shadowGenProgs[i]->SetUniform4fv(0, &shadowTexProjCenter.x);
485 shadowGenProgs[i]->Disable();
486 }
487 }
488
489 if (L->GetLightIntensity() > 0.0f) {
490 // move view into sun-space
491 const float3 camUp = camera->up;
492 const float3 camRgt = camera->right;
493
494 camera->right = sunDirX;
495 camera->up = sunDirY;
496
497 DrawShadowPasses();
498
499 camera->right = camRgt;
500 camera->up = camUp;
501 }
502
503 glShadeModel(GL_SMOOTH);
504 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
505
506 // we do this later to save render context switches (this is one of the slowest opengl operations!)
507 // fb.Unbind();
508 // glViewport(globalRendering->viewPosX,0,globalRendering->viewSizeX,globalRendering->viewSizeY);
509 }
510
511
512
GetShadowProjectionRadius(CCamera * cam,float3 & proPos,const float3 & proDir) const513 float CShadowHandler::GetShadowProjectionRadius(CCamera* cam, float3& proPos, const float3& proDir) const {
514 float radius = 1.0f;
515
516 switch (shadowProMode) {
517 case SHADOWPROMODE_CAM_CENTER: {
518 radius = GetOrthoProjectedFrustumRadius(cam, proPos);
519 } break;
520 case SHADOWPROMODE_MAP_CENTER: {
521 radius = GetOrthoProjectedMapRadius(proDir, proPos);
522 } break;
523 case SHADOWPROMODE_MIX_CAMMAP: {
524 static float3 opfPos;
525 static float3 opmPos;
526
527 const float opfRad = GetOrthoProjectedFrustumRadius(cam, opfPos);
528 const float opmRad = GetOrthoProjectedMapRadius(proDir, opmPos);
529
530 if (opfRad <= opmRad) { radius = opfRad; proPos = opfPos; }
531 if (opmRad <= opfRad) { radius = opmRad; proPos = opmPos; }
532 } break;
533 }
534
535 return radius;
536 }
537
GetOrthoProjectedMapRadius(const float3 & sunDir,float3 & projectionMidPos) const538 float CShadowHandler::GetOrthoProjectedMapRadius(const float3& sunDir, float3& projectionMidPos) const {
539 // to fit the map inside the frustum, we need to know
540 // the distance from one corner to its opposing corner
541 //
542 // this distance is maximal when the sun direction is
543 // orthogonal to the diagonal, but in other cases we
544 // can gain some precision by projecting the diagonal
545 // onto a vector orthogonal to the sun direction and
546 // using the length of that projected vector instead
547 //
548 // note: "radius" is actually the diameter
549 static const float maxMapDiameter = math::sqrtf(Square(gs->mapx * SQUARE_SIZE) + Square(gs->mapy * SQUARE_SIZE));
550 static float curMapDiameter = 0.0f;
551
552 static float3 sunDir3D = ZeroVector;
553
554 if ((sunDir3D != sunDir)) {
555 float3 sunDir2D;
556 float3 mapVerts[2];
557
558 sunDir3D = sunDir;
559 sunDir2D.x = sunDir3D.x;
560 sunDir2D.z = sunDir3D.z;
561 sunDir2D.ANormalize();
562
563 if (sunDir2D.x >= 0.0f) {
564 if (sunDir2D.z >= 0.0f) {
565 // use diagonal vector from top-right to bottom-left
566 mapVerts[0] = float3(gs->mapx * SQUARE_SIZE, 0.0f, 0.0f);
567 mapVerts[1] = float3( 0.0f, 0.0f, gs->mapy * SQUARE_SIZE);
568 } else {
569 // use diagonal vector from top-left to bottom-right
570 mapVerts[0] = float3( 0.0f, 0.0f, 0.0f);
571 mapVerts[1] = float3(gs->mapx * SQUARE_SIZE, 0.0f, gs->mapy * SQUARE_SIZE);
572 }
573 } else {
574 if (sunDir2D.z >= 0.0f) {
575 // use diagonal vector from bottom-right to top-left
576 mapVerts[0] = float3(gs->mapx * SQUARE_SIZE, 0.0f, gs->mapy * SQUARE_SIZE);
577 mapVerts[1] = float3( 0.0f, 0.0f, 0.0f);
578 } else {
579 // use diagonal vector from bottom-left to top-right
580 mapVerts[0] = float3( 0.0f, 0.0f, gs->mapy * SQUARE_SIZE);
581 mapVerts[1] = float3(gs->mapx * SQUARE_SIZE, 0.0f, 0.0f);
582 }
583 }
584
585 const float3 v1 = (mapVerts[1] - mapVerts[0]).ANormalize();
586 const float3 v2 = float3(-sunDir2D.z, 0.0f, sunDir2D.x);
587
588 curMapDiameter = maxMapDiameter * v2.dot(v1);
589
590 projectionMidPos.x = (gs->mapx * SQUARE_SIZE) * 0.5f;
591 projectionMidPos.z = (gs->mapy * SQUARE_SIZE) * 0.5f;
592 projectionMidPos.y = CGround::GetHeightReal(projectionMidPos.x, projectionMidPos.z, false);
593 }
594
595 return curMapDiameter;
596 }
597
GetOrthoProjectedFrustumRadius(CCamera * cam,float3 & projectionMidPos) const598 float CShadowHandler::GetOrthoProjectedFrustumRadius(CCamera* cam, float3& projectionMidPos) const {
599 cam->GetFrustumSides(0.0f, 0.0f, 1.0f, true);
600 cam->ClipFrustumLines(true, -10000.0f, 400096.0f);
601
602 const std::vector<CCamera::FrustumLine> sides = cam->GetNegFrustumSides();
603
604 if (sides.empty())
605 return 0.0f;
606
607 // two points per side; last point is used for the geometric average
608 // there are never more than 5 side-lines (10 points), so reserve 16
609 static std::vector<float3> frustumPoints(16, ZeroVector);
610
611 float3 frustumCenter = ZeroVector;
612 float frustumRadius = 0.0f;
613
614 for (unsigned int i = 0, j = 0; i < sides.size(); i++) {
615 const CCamera::FrustumLine* line = &sides[i];
616
617 if (line->minz < line->maxz) {
618 const float x0 = line->base + (line->dir * line->minz), z0 = line->minz;
619 const float x1 = line->base + (line->dir * line->maxz), z1 = line->maxz;
620
621 // TODO: smarter clamping
622 const float
623 cx0 = Clamp(x0, 0.0f, (float3::maxxpos + 1.0f)),
624 cz0 = Clamp(z0, 0.0f, (float3::maxzpos + 1.0f)),
625 cx1 = Clamp(x1, 0.0f, (float3::maxxpos + 1.0f)),
626 cz1 = Clamp(z1, 0.0f, (float3::maxzpos + 1.0f));
627
628 const float3 p0 = float3(cx0, CGround::GetHeightReal(cx0, cz0, false), cz0);
629 const float3 p1 = float3(cx1, CGround::GetHeightReal(cx1, cz1, false), cz1);
630
631 frustumPoints[j + 0] = p0;
632 frustumPoints[j + 1] = p1;
633 frustumCenter += p0;
634 frustumCenter += p1;
635
636 j += 2;
637 }
638 }
639
640 projectionMidPos.x = frustumCenter.x / (sides.size() * 2);
641 projectionMidPos.z = frustumCenter.z / (sides.size() * 2);
642 projectionMidPos.y = CGround::GetHeightReal(projectionMidPos.x, projectionMidPos.z, false);
643
644 // calculate the radius of the minimally-bounding sphere around the projected frustum
645 for (unsigned int n = 0; n < (sides.size() * 2); n++) {
646 const float3& pos = frustumPoints[n];
647 const float rad = (pos - projectionMidPos).SqLength();
648
649 frustumRadius = std::max(frustumRadius, rad);
650 }
651
652 static const float maxMapDiameter = math::sqrtf(Square(gs->mapx * SQUARE_SIZE) + Square(gs->mapy * SQUARE_SIZE));
653 const float frustumDiameter = math::sqrtf(frustumRadius) * 2.0f;
654
655 return std::min(maxMapDiameter, frustumDiameter);
656 }
657
658
659
660 #if 0
661 void CShadowHandler::CalcMinMaxView()
662 {
663 // derive the size of the shadow-map from the
664 // intersection points of the camera frustum
665 // with the xz-plane
666 cam2->GetFrustumSides(0.0f, 0.0f, 1.0f, true);
667 cam2->ClipFrustumLines(true, -20000.0f, gs->mapy * SQUARE_SIZE + 20000.0f);
668
669 shadowProjMinMax.x = -100.0f;
670 shadowProjMinMax.y = 100.0f;
671 shadowProjMinMax.z = -100.0f;
672 shadowProjMinMax.w = 100.0f;
673
674 //if someone could figure out how the frustum and nonlinear shadow transform really works (and not use the SJan trial and error method)
675 //so that we can skip this sort of fudge factors it would be good
676 float borderSize = 270.0f;
677 float maxSize = globalRendering->viewRange * 0.75f;
678
679 if (shadowMapSize == 1024) {
680 borderSize *= 1.5f;
681 maxSize *= 1.2f;
682 }
683
684 const std::vector<CCamera::FrustumLine> negSides = cam2->GetNegFrustumSides();
685 const std::vector<CCamera::FrustumLine> posSides = cam2->GetPosFrustumSides();
686 std::vector<CCamera::FrustumLine>::const_iterator fli;
687
688 if (!negSides.empty()) {
689 for (fli = negSides.begin(); fli != negSides.end(); ++fli) {
690 if (fli->minz < fli->maxz) {
691 float3 p[5];
692 p[0] = float3(fli->base + fli->dir * fli->minz, 0.0f, fli->minz);
693 p[1] = float3(fli->base + fli->dir * fli->maxz, 0.0f, fli->maxz);
694 p[2] = float3(fli->base + fli->dir * fli->minz, readMap->initMaxHeight + 200, fli->minz);
695 p[3] = float3(fli->base + fli->dir * fli->maxz, readMap->initMaxHeight + 200, fli->maxz);
696 p[4] = centerPos;
697
698 for (int a = 0; a < 5; ++a) {
699 const float xd = (p[a] - centerPos).dot(sunDirX);
700 const float yd = (p[a] - centerPos).dot(sunDirY);
701
702 if (xd + borderSize > shadowProjMinMax.y) { shadowProjMinMax.y = xd + borderSize; }
703 if (xd - borderSize < shadowProjMinMax.x) { shadowProjMinMax.x = xd - borderSize; }
704 if (yd + borderSize > shadowProjMinMax.w) { shadowProjMinMax.w = yd + borderSize; }
705 if (yd - borderSize < shadowProjMinMax.z) { shadowProjMinMax.z = yd - borderSize; }
706 }
707 }
708 }
709
710 if (shadowProjMinMax.x < -maxSize) { shadowProjMinMax.x = -maxSize; }
711 if (shadowProjMinMax.y > maxSize) { shadowProjMinMax.y = maxSize; }
712 if (shadowProjMinMax.z < -maxSize) { shadowProjMinMax.z = -maxSize; }
713 if (shadowProjMinMax.w > maxSize) { shadowProjMinMax.w = maxSize; }
714 } else {
715 shadowProjMinMax.x = -maxSize;
716 shadowProjMinMax.y = maxSize;
717 shadowProjMinMax.z = -maxSize;
718 shadowProjMinMax.w = maxSize;
719 }
720
721 // xScale = (shadowProjMinMax.y - shadowProjMinMax.x) * 1.5f;
722 // yScale = (shadowProjMinMax.w - shadowProjMinMax.z) * 1.5f;
723 }
724 #endif
725
726