Index: binaries/data/config/default.cfg =================================================================== --- binaries/data/config/default.cfg +++ binaries/data/config/default.cfg @@ -72,6 +72,9 @@ shadowquality = 0 ; Shadow map resolution. (-2 - Very Low, -1 - Low, 0 - Medium, 1 - High, 2 - Very High) ; High values can crash the game when using a graphics card with low memory! shadowpcf = true +shadowfixed = false ; When enabled shadows are rendered only on the +shadowfixeddistance = 300.0 ; fixed distance and without swimming effect. + vsync = false particles = true fog = true Index: source/renderer/ShadowMap.cpp =================================================================== --- source/renderer/ShadowMap.cpp +++ source/renderer/ShadowMap.cpp @@ -78,6 +78,10 @@ CBoundingBoxAligned ShadowRenderBound; + CBoundingBoxAligned FixedFrustumBounds; + bool FixedShadowsEnabled; + float FixedShadowDistance; + // Camera transformed into light space CCamera LightspaceCamera; @@ -122,6 +126,11 @@ // Avoid using uninitialised values in AddShadowedBound if SetupFrame wasn't called first m->LightTransform.SetIdentity(); + + m->FixedShadowsEnabled = false; + m->FixedShadowDistance = 300.0f; + CFG_GET_VAL("shadowfixed", m->FixedShadowsEnabled); + CFG_GET_VAL("shadowfixeddistance", m->FixedShadowDistance); } @@ -163,10 +172,17 @@ if (!m->Texture) m->CreateTexture(); - CVector3D z = lightdir; - CVector3D y; - CVector3D x = camera.m_Orientation.GetIn(); - CVector3D eyepos = camera.m_Orientation.GetTranslation(); + CVector3D x, y, z = lightdir; + CVector3D eyepos; + if (m->FixedShadowsEnabled) + { + x = CVector3D(0, 1, 0); + } + else + { + x = camera.m_Orientation.GetIn(); + eyepos = camera.m_Orientation.GetTranslation(); + } z.Normalize(); x -= z * z.Dot(x); @@ -213,6 +229,42 @@ m->LightspaceCamera = camera; m->LightspaceCamera.m_Orientation = m->LightTransform * camera.m_Orientation; m->LightspaceCamera.UpdateFrustum(); + + if (m->FixedShadowsEnabled) + { + // We need to calculate a circumscribed sphere for the camera to + // create a rotation stable bounding box. + const CVector3D cameraIn = camera.m_Orientation.GetIn(); + const CVector3D cameraTranslation = camera.m_Orientation.GetTranslation(); + CVector3D centerNear = cameraTranslation + cameraIn * camera.GetNearPlane(); + CVector3D centerDist = cameraTranslation + cameraIn * m->FixedShadowDistance; + + // We can solve 3D problem in 2D space, because the frustum is + // symmetric by 2 planes. Than means we can use only one corner + // to find a circumscribed sphere. + CCamera::Quad tmp; + camera.GetViewQuad(camera.GetNearPlane(), tmp); + CVector3D cornerNear = camera.GetOrientation().Transform(tmp[0]); + camera.GetViewQuad(m->FixedShadowDistance, tmp); + CVector3D cornerDist = camera.GetOrientation().Transform(tmp[0]); + + // We solve 2D case for the right trapezoid. + const float a = (cornerNear - centerNear).Length(); // First base. + const float b = (cornerDist - centerDist).Length(); // Second base. + const float d = (centerDist - centerNear).Length(); // Height. + const float x = (d * d + b * b - a * a) * 0.5f / d; + + CVector3D position = cameraTranslation + cameraIn * (camera.GetNearPlane() + x); + const float radius = (cornerNear - position).Length(); + + // We need to convert the bounding box to the light space. + position = m->LightTransform.Rotate(position); + + const float insets = 0.2f; + m->FixedFrustumBounds = CBoundingBoxAligned(position, position); + m->FixedFrustumBounds.Expand(radius); + m->FixedFrustumBounds.Expand(insets); + } } @@ -268,43 +320,57 @@ // projection and transformation matrices void ShadowMapInternals::CalcShadowMatrices() { - // Start building the shadow map to cover all objects that will receive shadows - CBoundingBoxAligned receiverBound = ShadowReceiverBound; + if (FixedShadowsEnabled) + { + ShadowRenderBound = FixedFrustumBounds; - // Intersect with the camera frustum, so the shadow map doesn't have to get - // stretched to cover the off-screen parts of large models - receiverBound.IntersectFrustumConservative(LightspaceCamera.GetFrustum()); + // Set the near and far planes to include just the shadow casters, + // so we make full use of the depth texture's range. Add a bit of a + // delta so we don't accidentally clip objects that are directly on + // the planes. + ShadowRenderBound[0].Z = ShadowCasterBound[0].Z - 2.f; + ShadowRenderBound[1].Z = ShadowCasterBound[1].Z + 2.f; + } + else + { + // Start building the shadow map to cover all objects that will receive shadows + CBoundingBoxAligned receiverBound = ShadowReceiverBound; - // Intersect with the shadow caster bounds, because there's no point - // wasting space around the edges of the shadow map that we're not going - // to draw into - ShadowRenderBound[0].X = std::max(receiverBound[0].X, ShadowCasterBound[0].X); - ShadowRenderBound[0].Y = std::max(receiverBound[0].Y, ShadowCasterBound[0].Y); - ShadowRenderBound[1].X = std::min(receiverBound[1].X, ShadowCasterBound[1].X); - ShadowRenderBound[1].Y = std::min(receiverBound[1].Y, ShadowCasterBound[1].Y); - - // Set the near and far planes to include just the shadow casters, - // so we make full use of the depth texture's range. Add a bit of a - // delta so we don't accidentally clip objects that are directly on - // the planes. - ShadowRenderBound[0].Z = ShadowCasterBound[0].Z - 2.f; - ShadowRenderBound[1].Z = ShadowCasterBound[1].Z + 2.f; + // Intersect with the camera frustum, so the shadow map doesn't have to get + // stretched to cover the off-screen parts of large models + receiverBound.IntersectFrustumConservative(LightspaceCamera.GetFrustum()); + + // Intersect with the shadow caster bounds, because there's no point + // wasting space around the edges of the shadow map that we're not going + // to draw into + ShadowRenderBound[0].X = std::max(receiverBound[0].X, ShadowCasterBound[0].X); + ShadowRenderBound[0].Y = std::max(receiverBound[0].Y, ShadowCasterBound[0].Y); + ShadowRenderBound[1].X = std::min(receiverBound[1].X, ShadowCasterBound[1].X); + ShadowRenderBound[1].Y = std::min(receiverBound[1].Y, ShadowCasterBound[1].Y); + + // Set the near and far planes to include just the shadow casters, + // so we make full use of the depth texture's range. Add a bit of a + // delta so we don't accidentally clip objects that are directly on + // the planes. + ShadowRenderBound[0].Z = ShadowCasterBound[0].Z - 2.f; + ShadowRenderBound[1].Z = ShadowCasterBound[1].Z + 2.f; - // ShadowBound might have been empty to begin with, producing an empty result - if (ShadowRenderBound.IsEmpty()) - { - // no-op - LightProjection.SetIdentity(); - TextureMatrix = LightTransform; - return; - } + // ShadowBound might have been empty to begin with, producing an empty result + if (ShadowRenderBound.IsEmpty()) + { + // no-op + LightProjection.SetIdentity(); + TextureMatrix = LightTransform; + return; + } - // round off the shadow boundaries to sane increments to help reduce swim effect - float boundInc = 16.0f; - ShadowRenderBound[0].X = floor(ShadowRenderBound[0].X / boundInc) * boundInc; - ShadowRenderBound[0].Y = floor(ShadowRenderBound[0].Y / boundInc) * boundInc; - ShadowRenderBound[1].X = ceil(ShadowRenderBound[1].X / boundInc) * boundInc; - ShadowRenderBound[1].Y = ceil(ShadowRenderBound[1].Y / boundInc) * boundInc; + // round off the shadow boundaries to sane increments to help reduce swim effect + float boundInc = 16.0f; + ShadowRenderBound[0].X = floor(ShadowRenderBound[0].X / boundInc) * boundInc; + ShadowRenderBound[0].Y = floor(ShadowRenderBound[0].Y / boundInc) * boundInc; + ShadowRenderBound[1].X = ceil(ShadowRenderBound[1].X / boundInc) * boundInc; + ShadowRenderBound[1].Y = ceil(ShadowRenderBound[1].Y / boundInc) * boundInc; + } // Setup orthogonal projection (lightspace -> clip space) for shadowmap rendering CVector3D scale = ShadowRenderBound[1] - ShadowRenderBound[0]; @@ -530,7 +596,6 @@ } } - /////////////////////////////////////////////////////////////////////////////////////////////////// // Set up to render into shadow map texture void ShadowMap::BeginRender()