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