1
1
forked from 0ad/0ad

Adds optional fixed shadows without swimming effect

Fixed shadows have fixed rendering distance. Also they don't have a
swimming (blinking) effect on camera movement/rotation.

Comments By: Stan
Differential Revision: https://code.wildfiregames.com/D2465
This was SVN commit r23333.
This commit is contained in:
Vladislav Belov 2020-01-05 22:44:39 +00:00
parent e7caa135e0
commit 8e48e1b17e
2 changed files with 109 additions and 42 deletions

View File

@ -72,6 +72,9 @@ shadows = true
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
shadowsfixed = false ; When enabled shadows are rendered only on the
shadowsfixeddistance = 300.0 ; fixed distance and without swimming effect.
vsync = false
particles = true
fog = true

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2019 Wildfire Games.
/* Copyright (C) 2020 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -78,6 +78,10 @@ struct ShadowMapInternals
CBoundingBoxAligned ShadowRenderBound;
CBoundingBoxAligned FixedFrustumBounds;
bool FixedShadowsEnabled;
float FixedShadowsDistance;
// Camera transformed into light space
CCamera LightspaceCamera;
@ -122,6 +126,11 @@ ShadowMap::ShadowMap()
// Avoid using uninitialised values in AddShadowedBound if SetupFrame wasn't called first
m->LightTransform.SetIdentity();
m->FixedShadowsEnabled = false;
m->FixedShadowsDistance = 300.0f;
CFG_GET_VAL("shadowsfixed", m->FixedShadowsEnabled);
CFG_GET_VAL("shadowsfixeddistance", m->FixedShadowsDistance);
}
@ -163,11 +172,16 @@ void ShadowMap::SetupFrame(const CCamera& camera, const CVector3D& lightdir)
if (!m->Texture)
m->CreateTexture();
CVector3D z = lightdir;
CVector3D y;
CVector3D x = camera.m_Orientation.GetIn();
CVector3D eyepos = camera.m_Orientation.GetTranslation();
CVector3D x, eyepos;
if (!m->FixedShadowsEnabled)
{
x = camera.m_Orientation.GetIn();
eyepos = camera.m_Orientation.GetTranslation();
}
else
x = CVector3D(0, 1, 0);
CVector3D z = lightdir;
z.Normalize();
x -= z * z.Dot(x);
if (x.Length() < 0.001)
@ -178,7 +192,7 @@ void ShadowMap::SetupFrame(const CCamera& camera, const CVector3D& lightdir)
x -= z * z.Dot(x);
}
x.Normalize();
y = z.Cross(x);
CVector3D y = z.Cross(x);
// X axis perpendicular to light direction, flowing along with view direction
m->LightTransform._11 = x.X;
@ -213,6 +227,43 @@ void ShadowMap::SetupFrame(const CCamera& camera, const CVector3D& lightdir)
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();
const CVector3D centerNear = cameraTranslation + cameraIn * camera.GetNearPlane();
const CVector3D centerDist = cameraTranslation + cameraIn * m->FixedShadowsDistance;
// 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 corners;
camera.GetViewQuad(camera.GetNearPlane(), corners);
const CVector3D cornerNear = camera.GetOrientation().Transform(corners[0]);
camera.GetViewQuad(m->FixedShadowsDistance, corners);
const CVector3D cornerDist = camera.GetOrientation().Transform(corners[0]);
// We solve 2D case for the right trapezoid.
const float firstBase = (cornerNear - centerNear).Length();
const float secondBase = (cornerDist - centerDist).Length();
const float height = (centerDist - centerNear).Length();
const float distanceToCenter =
(height * height + secondBase * secondBase - firstBase * firstBase) * 0.5f / height;
CVector3D position = cameraTranslation + cameraIn * (camera.GetNearPlane() + distanceToCenter);
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 +319,57 @@ CFrustum ShadowMap::GetShadowCasterCullFrustum()
// projection and transformation matrices
void ShadowMapInternals::CalcShadowMatrices()
{
// Start building the shadow map to cover all objects that will receive shadows
CBoundingBoxAligned receiverBound = ShadowReceiverBound;
// 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())
if (FixedShadowsEnabled)
{
// no-op
LightProjection.SetIdentity();
TextureMatrix = LightTransform;
return;
}
ShadowRenderBound = FixedFrustumBounds;
// 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;
// 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 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;
}
// 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 +595,6 @@ void ShadowMapInternals::CreateTexture()
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// Set up to render into shadow map texture
void ShadowMap::BeginRender()